Compare commits
20 Commits
Author | SHA1 | Date | |
---|---|---|---|
4225abe3c4 | |||
74fdb58f84 | |||
bffdaffe39 | |||
67a4228518 | |||
681209f2e1 | |||
c415a6c361 | |||
009e3c4f0e | |||
f9c42975dc | |||
feef949afe | |||
8d3b07b1e6 | |||
51fe935f1f | |||
146fac73cf | |||
4465cac807 | |||
9d7ed21cba | |||
54fbe5beac | |||
0704853fa2 | |||
8cf22ee38b | |||
f28e68e487 | |||
499aed19f6 | |||
618b6fe2d1 |
77
changelog.md
77
changelog.md
@ -1,5 +1,82 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 2025-03-11 - 3.30.6 - fix(PortProxy)
|
||||||
|
Improve TLS renegotiation handling in PortProxy by validating the new SNI against allowed domain configurations. If the new SNI is permitted based on existing IP rules, update the locked domain to allow connection reuse; otherwise, terminate the connection to prevent misrouting.
|
||||||
|
|
||||||
|
- Added logic to check if a new SNI during renegotiation is allowed by comparing IP rules from the matching domain configuration.
|
||||||
|
- Updated detailed logging to indicate when a valid SNI change is accepted and when it results in a mismatch termination.
|
||||||
|
|
||||||
|
## 2025-03-10 - 3.30.5 - fix(internal)
|
||||||
|
No uncommitted changes detected; project files and tests remain unchanged.
|
||||||
|
|
||||||
|
|
||||||
|
## 2025-03-10 - 3.30.4 - fix(PortProxy)
|
||||||
|
Fix TLS renegotiation handling and adjust TLS keep-alive timeouts in PortProxy implementation
|
||||||
|
|
||||||
|
- Allow TLS renegotiation data without an explicit SNI extraction to pass through, ensuring valid renegotiations are not dropped (critical for Chrome).
|
||||||
|
- Update TLS keep-alive timeout from an aggressive 30 minutes to a more generous 4 hours to reduce unnecessary reconnections.
|
||||||
|
- Increase inactivity thresholds for TLS connections from 20 minutes to 2 hours with an additional verification interval extended from 5 to 15 minutes.
|
||||||
|
- Adjust long-lived TLS connection timeout from 45 minutes to 8 hours for improved certificate context refresh in chained proxy scenarios.
|
||||||
|
|
||||||
|
## 2025-03-10 - 3.30.3 - fix(classes.portproxy.ts)
|
||||||
|
Simplify timeout management in PortProxy and fix chained proxy certificate refresh issues
|
||||||
|
|
||||||
|
- Reduced TLS keep-alive timeout from 8 hours to 30 minutes to ensure frequent certificate refresh
|
||||||
|
- Added aggressive TLS state refresh after 20 minutes of inactivity and secondary verification checks
|
||||||
|
- Lowered long-lived TLS connection lifetime from 12 hours to 45 minutes to prevent stale certificates
|
||||||
|
- Removed configurable timeout settings from the public API in favor of hardcoded sensible defaults
|
||||||
|
- Simplified internal timeout management to reduce code complexity and improve certificate handling in chained proxies
|
||||||
|
|
||||||
|
## 2025-03-10 - 3.31.0 - fix(classes.portproxy.ts)
|
||||||
|
Simplified timeout management and fixed certificate issues in chained proxy scenarios
|
||||||
|
|
||||||
|
- Dramatically reduced TLS keep-alive timeout from 8 hours to 30 minutes to ensure fresh certificates
|
||||||
|
- Added aggressive certificate refresh after 20 minutes of inactivity (down from 4 hours)
|
||||||
|
- Added secondary verification checks for TLS refresh operations
|
||||||
|
- Reduced long-lived TLS connection lifetime from 12 hours to 45 minutes
|
||||||
|
- Removed configurable timeouts completely from the public API in favor of hardcoded sensible defaults
|
||||||
|
- Simplified interface by removing no-longer-configurable settings while maintaining internal compatibility
|
||||||
|
- Reduced overall code complexity by eliminating complex timeout management
|
||||||
|
- Fixed chained proxy certificate issues by ensuring more frequent certificate refreshes in all deployment scenarios
|
||||||
|
|
||||||
|
## 2025-03-10 - 3.30.2 - fix(classes.portproxy.ts)
|
||||||
|
Adjust TLS keep-alive timeout to refresh certificate context.
|
||||||
|
|
||||||
|
- Modified TLS keep-alive timeout for connections to 8 hours to refresh certificate context.
|
||||||
|
- Updated timeout log messages for clarity on TLS certificate refresh.
|
||||||
|
|
||||||
|
## 2025-03-10 - 3.30.1 - fix(PortProxy)
|
||||||
|
Improve TLS keep-alive management and fix whitespace formatting
|
||||||
|
|
||||||
|
- Implemented better handling for TLS keep-alive connections after sleep or long inactivity.
|
||||||
|
- Reformatted whitespace for better readability and consistency.
|
||||||
|
|
||||||
|
## 2025-03-08 - 3.30.0 - feat(PortProxy)
|
||||||
|
Add advanced TLS keep-alive handling and system sleep detection
|
||||||
|
|
||||||
|
- Implemented system sleep detection to maintain keep-alive connections.
|
||||||
|
- Enhanced TLS keep-alive connections with extended timeout and sleep detection mechanisms.
|
||||||
|
- Introduced automatic TLS state refresh after system wake-up to prevent connection drops.
|
||||||
|
|
||||||
|
## 2025-03-07 - 3.29.3 - fix(core)
|
||||||
|
Fix functional errors in the proxy setup and enhance pnpm configuration
|
||||||
|
|
||||||
|
- Corrected pnpm configuration to include specific dependencies as 'onlyBuiltDependencies'.
|
||||||
|
|
||||||
|
## 2025-03-07 - 3.29.2 - fix(PortProxy)
|
||||||
|
Fix test for PortProxy handling of custom IPs in Docker/CI environments.
|
||||||
|
|
||||||
|
- Ensure compatibility with Docker/CI environments by standardizing on 127.0.0.1 for test server setup.
|
||||||
|
- Simplify test configuration by using a unique port rather than different IPs.
|
||||||
|
|
||||||
|
## 2025-03-07 - 3.29.1 - fix(readme)
|
||||||
|
Update readme for IPTablesProxy options
|
||||||
|
|
||||||
|
- Add comprehensive examples for IPTablesProxy usage.
|
||||||
|
- Expand IPTablesProxy settings with IPv6, logging, and advanced features.
|
||||||
|
- Clarify option defaults and descriptions for IPTablesProxy.
|
||||||
|
- Enhance 'Troubleshooting' section with IPTables tips.
|
||||||
|
|
||||||
## 2025-03-07 - 3.29.0 - feat(IPTablesProxy)
|
## 2025-03-07 - 3.29.0 - feat(IPTablesProxy)
|
||||||
Enhanced IPTablesProxy with multi-port and IPv6 support
|
Enhanced IPTablesProxy with multi-port and IPv6 support
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@push.rocks/smartproxy",
|
"name": "@push.rocks/smartproxy",
|
||||||
"version": "3.29.0",
|
"version": "3.30.6",
|
||||||
"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, and dynamic routing with authentication options.",
|
"description": "A powerful proxy package that effectively handles high traffic, with features such as SSL/TLS support, port proxying, WebSocket handling, and dynamic routing with authentication options.",
|
||||||
"main": "dist_ts/index.js",
|
"main": "dist_ts/index.js",
|
||||||
@ -77,6 +77,11 @@
|
|||||||
"url": "https://code.foss.global/push.rocks/smartproxy/issues"
|
"url": "https://code.foss.global/push.rocks/smartproxy/issues"
|
||||||
},
|
},
|
||||||
"pnpm": {
|
"pnpm": {
|
||||||
"overrides": {}
|
"overrides": {},
|
||||||
|
"onlyBuiltDependencies": [
|
||||||
|
"esbuild",
|
||||||
|
"mongodb-memory-server",
|
||||||
|
"puppeteer"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
1863
pnpm-lock.yaml
generated
1863
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
84
readme.md
84
readme.md
@ -320,8 +320,8 @@ portProxy.start();
|
|||||||
```typescript
|
```typescript
|
||||||
import { IPTablesProxy } from '@push.rocks/smartproxy';
|
import { IPTablesProxy } from '@push.rocks/smartproxy';
|
||||||
|
|
||||||
// Configure IPTables to forward from port 80 to 8080
|
// Basic usage - forward single port
|
||||||
const iptables = new IPTablesProxy({
|
const basicProxy = new IPTablesProxy({
|
||||||
fromPort: 80,
|
fromPort: 80,
|
||||||
toPort: 8080,
|
toPort: 8080,
|
||||||
toHost: 'localhost',
|
toHost: 'localhost',
|
||||||
@ -329,7 +329,38 @@ const iptables = new IPTablesProxy({
|
|||||||
deleteOnExit: true // Automatically clean up rules on process exit
|
deleteOnExit: true // Automatically clean up rules on process exit
|
||||||
});
|
});
|
||||||
|
|
||||||
iptables.start();
|
// Forward port ranges
|
||||||
|
const rangeProxy = new IPTablesProxy({
|
||||||
|
fromPort: { from: 3000, to: 3010 }, // Forward ports 3000-3010
|
||||||
|
toPort: { from: 8000, to: 8010 }, // To ports 8000-8010
|
||||||
|
protocol: 'tcp', // TCP protocol (default)
|
||||||
|
ipv6Support: true, // Enable IPv6 support
|
||||||
|
enableLogging: true // Enable detailed logging
|
||||||
|
});
|
||||||
|
|
||||||
|
// Multiple port specifications with IP filtering
|
||||||
|
const advancedProxy = new IPTablesProxy({
|
||||||
|
fromPort: [80, 443, { from: 8000, to: 8010 }], // Multiple ports/ranges
|
||||||
|
toPort: [8080, 8443, { from: 18000, to: 18010 }],
|
||||||
|
allowedSourceIPs: ['10.0.0.0/8', '192.168.1.0/24'], // Only allow these IPs
|
||||||
|
bannedSourceIPs: ['192.168.1.100'], // Explicitly block these IPs
|
||||||
|
addJumpRule: true, // Use custom chain for better management
|
||||||
|
checkExistingRules: true // Check for duplicate rules
|
||||||
|
});
|
||||||
|
|
||||||
|
// NetworkProxy integration for SSL termination
|
||||||
|
const sslProxy = new IPTablesProxy({
|
||||||
|
fromPort: 443,
|
||||||
|
toPort: 8443,
|
||||||
|
netProxyIntegration: {
|
||||||
|
enabled: true,
|
||||||
|
redirectLocalhost: true, // Redirect localhost traffic to NetworkProxy
|
||||||
|
sslTerminationPort: 8443 // Port where NetworkProxy handles SSL
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Start any of the proxies
|
||||||
|
await basicProxy.start();
|
||||||
```
|
```
|
||||||
|
|
||||||
### Automatic HTTPS Certificate Management
|
### Automatic HTTPS Certificate Management
|
||||||
@ -384,12 +415,29 @@ acmeHandler.addDomain('api.example.com');
|
|||||||
### IPTablesProxy Settings
|
### IPTablesProxy Settings
|
||||||
|
|
||||||
| Option | Description | Default |
|
| Option | Description | Default |
|
||||||
|-------------------|---------------------------------------------|-------------|
|
|-----------------------|---------------------------------------------------|-------------|
|
||||||
| `fromPort` | Source port to forward from | - |
|
| `fromPort` | Source port(s) or range(s) to forward from | - |
|
||||||
| `toPort` | Destination port to forward to | - |
|
| `toPort` | Destination port(s) or range(s) to forward to | - |
|
||||||
| `toHost` | Destination host to forward to | 'localhost' |
|
| `toHost` | Destination host to forward to | 'localhost' |
|
||||||
| `preserveSourceIP`| Preserve the original client IP | false |
|
| `preserveSourceIP` | Preserve the original client IP | false |
|
||||||
| `deleteOnExit` | Remove iptables rules when process exits | false |
|
| `deleteOnExit` | Remove iptables rules when process exits | false |
|
||||||
|
| `protocol` | Protocol to forward ('tcp', 'udp', or 'all') | 'tcp' |
|
||||||
|
| `enableLogging` | Enable detailed logging | false |
|
||||||
|
| `ipv6Support` | Enable IPv6 support with ip6tables | false |
|
||||||
|
| `allowedSourceIPs` | Array of IP addresses/CIDR allowed to connect | - |
|
||||||
|
| `bannedSourceIPs` | Array of IP addresses/CIDR blocked from connecting | - |
|
||||||
|
| `forceCleanSlate` | Clear all IPTablesProxy rules before starting | false |
|
||||||
|
| `addJumpRule` | Add a custom chain for cleaner rule management | false |
|
||||||
|
| `checkExistingRules` | Check if rules already exist before adding | true |
|
||||||
|
| `netProxyIntegration` | NetworkProxy integration options (object) | - |
|
||||||
|
|
||||||
|
#### IPTablesProxy NetworkProxy Integration Options
|
||||||
|
|
||||||
|
| Option | Description | Default |
|
||||||
|
|----------------------|---------------------------------------------------|---------|
|
||||||
|
| `enabled` | Enable NetworkProxy integration | false |
|
||||||
|
| `redirectLocalhost` | Redirect localhost traffic to NetworkProxy | false |
|
||||||
|
| `sslTerminationPort` | Port where NetworkProxy handles SSL termination | - |
|
||||||
|
|
||||||
## Advanced Features
|
## Advanced Features
|
||||||
|
|
||||||
@ -442,6 +490,18 @@ The `PortProxy` class can inspect the SNI (Server Name Indication) field in TLS
|
|||||||
- Domain-specific allowed IP ranges
|
- Domain-specific allowed IP ranges
|
||||||
- Protection against SNI renegotiation attacks
|
- Protection against SNI renegotiation attacks
|
||||||
|
|
||||||
|
### Enhanced IPTables Management
|
||||||
|
|
||||||
|
The improved `IPTablesProxy` class offers advanced capabilities:
|
||||||
|
|
||||||
|
- Support for multiple port ranges and individual ports
|
||||||
|
- IPv6 support with ip6tables
|
||||||
|
- Source IP filtering with allow/block lists
|
||||||
|
- Custom chain creation for better rule organization
|
||||||
|
- NetworkProxy integration for SSL termination
|
||||||
|
- Automatic rule existence checking to prevent duplicates
|
||||||
|
- Comprehensive cleanup on shutdown
|
||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|
||||||
### Browser Certificate Errors
|
### Browser Certificate Errors
|
||||||
@ -475,6 +535,16 @@ For improved connection stability in high-traffic environments:
|
|||||||
4. **Monitor Connection Statistics**: Enable detailed logging to track termination reasons
|
4. **Monitor Connection Statistics**: Enable detailed logging to track termination reasons
|
||||||
5. **Fine-tune Inactivity Checks**: Adjust `inactivityCheckInterval` based on your traffic patterns
|
5. **Fine-tune Inactivity Checks**: Adjust `inactivityCheckInterval` based on your traffic patterns
|
||||||
|
|
||||||
|
### IPTables Troubleshooting
|
||||||
|
|
||||||
|
If you're experiencing issues with IPTablesProxy:
|
||||||
|
|
||||||
|
1. **Enable Detailed Logging**: Set `enableLogging: true` to see all rule operations
|
||||||
|
2. **Force Clean Slate**: Use `forceCleanSlate: true` to remove any lingering rules
|
||||||
|
3. **Use Custom Chains**: Enable `addJumpRule: true` for cleaner rule management
|
||||||
|
4. **Check Permissions**: Ensure your process has sufficient permissions to modify iptables
|
||||||
|
5. **Verify IPv6 Support**: If using `ipv6Support: true`, ensure ip6tables is available
|
||||||
|
|
||||||
## License and Legal Information
|
## License and Legal Information
|
||||||
|
|
||||||
This repository contains open-source code that is licensed under the MIT License. A copy of the MIT License can be found in the [license](license) file within this repository.
|
This repository contains open-source code that is licensed under the MIT License. A copy of the MIT License can be found in the [license](license) file within this repository.
|
||||||
|
@ -113,20 +113,21 @@ tap.test('should forward TCP connections to custom host', async () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Test custom IP forwarding
|
// Test custom IP forwarding
|
||||||
// SIMPLIFIED: This version avoids port ranges and domain configs to prevent loops
|
// Modified to work in Docker/CI environments without needing 127.0.0.2
|
||||||
tap.test('should forward connections to custom IP', async () => {
|
tap.test('should forward connections to custom IP', async () => {
|
||||||
// Set up ports that are FAR apart to avoid any possible confusion
|
// Set up ports that are FAR apart to avoid any possible confusion
|
||||||
const forcedProxyPort = PROXY_PORT + 2; // 4003 - The port that our proxy listens on
|
const forcedProxyPort = PROXY_PORT + 2; // 4003 - The port that our proxy listens on
|
||||||
const targetServerPort = TEST_SERVER_PORT + 200; // 4200 - Target test server on another IP
|
const targetServerPort = TEST_SERVER_PORT + 200; // 4200 - Target test server on different port
|
||||||
|
|
||||||
// Create a test server listening on 127.0.0.2:4200
|
// Create a test server listening on a unique port on 127.0.0.1 (works in all environments)
|
||||||
const testServer2 = await createTestServer(targetServerPort, '127.0.0.2');
|
const testServer2 = await createTestServer(targetServerPort, '127.0.0.1');
|
||||||
|
|
||||||
// Simplify the test drastically - use ONE proxy with very explicit configuration
|
// We're simulating routing to a different IP by using a different port
|
||||||
|
// This tests the core functionality without requiring multiple IPs
|
||||||
const domainProxy = new PortProxy({
|
const domainProxy = new PortProxy({
|
||||||
fromPort: forcedProxyPort, // 4003 - Listen on this port
|
fromPort: forcedProxyPort, // 4003 - Listen on this port
|
||||||
toPort: targetServerPort, // 4200 - Default forwarding port - MUST BE DIFFERENT from fromPort
|
toPort: targetServerPort, // 4200 - Forward to this port
|
||||||
targetIP: '127.0.0.2', // Forward to IP where test server is
|
targetIP: '127.0.0.1', // Always use localhost (works in Docker)
|
||||||
domainConfigs: [], // No domain configs to confuse things
|
domainConfigs: [], // No domain configs to confuse things
|
||||||
sniEnabled: false,
|
sniEnabled: false,
|
||||||
defaultAllowedIPs: ['127.0.0.1', '::ffff:127.0.0.1'], // Allow localhost
|
defaultAllowedIPs: ['127.0.0.1', '::ffff:127.0.0.1'], // Allow localhost
|
||||||
|
@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@push.rocks/smartproxy',
|
name: '@push.rocks/smartproxy',
|
||||||
version: '3.29.0',
|
version: '3.30.6',
|
||||||
description: 'A powerful proxy package that effectively handles high traffic, with features such as SSL/TLS support, port proxying, WebSocket handling, and dynamic routing with authentication options.'
|
description: 'A powerful proxy package that effectively handles high traffic, with features such as SSL/TLS support, port proxying, WebSocket handling, and dynamic routing with authentication options.'
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,12 @@ export interface IDomainConfig {
|
|||||||
networkProxyIndex?: number; // Optional index to specify which NetworkProxy to use (defaults to 0)
|
networkProxyIndex?: number; // Optional index to specify which NetworkProxy to use (defaults to 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Port proxy settings including global allowed port ranges */
|
/**
|
||||||
|
* Port proxy settings including global allowed port ranges
|
||||||
|
*
|
||||||
|
* NOTE: In version 3.31.0+, timeout settings have been simplified and hardcoded with sensible defaults
|
||||||
|
* to ensure TLS certificate safety in all deployment scenarios, especially chained proxies.
|
||||||
|
*/
|
||||||
export interface IPortProxySettings extends plugins.tls.TlsOptions {
|
export interface IPortProxySettings extends plugins.tls.TlsOptions {
|
||||||
fromPort: number;
|
fromPort: number;
|
||||||
toPort: number;
|
toPort: number;
|
||||||
@ -27,14 +32,10 @@ export interface IPortProxySettings extends plugins.tls.TlsOptions {
|
|||||||
defaultBlockedIPs?: string[];
|
defaultBlockedIPs?: string[];
|
||||||
preserveSourceIP?: boolean;
|
preserveSourceIP?: boolean;
|
||||||
|
|
||||||
// Timeout settings
|
// Simplified timeout settings
|
||||||
initialDataTimeout?: number; // Timeout for initial data/SNI (ms), default: 60000 (60s)
|
|
||||||
socketTimeout?: number; // Socket inactivity timeout (ms), default: 3600000 (1h)
|
|
||||||
inactivityCheckInterval?: number; // How often to check for inactive connections (ms), default: 60000 (60s)
|
|
||||||
maxConnectionLifetime?: number; // Default max connection lifetime (ms), default: 86400000 (24h)
|
|
||||||
inactivityTimeout?: number; // Inactivity timeout (ms), default: 14400000 (4h)
|
|
||||||
|
|
||||||
gracefulShutdownTimeout?: number; // (ms) maximum time to wait for connections to close during shutdown
|
gracefulShutdownTimeout?: number; // (ms) maximum time to wait for connections to close during shutdown
|
||||||
|
|
||||||
|
// Ranged port settings
|
||||||
globalPortRanges: Array<{ from: number; to: number }>; // Global allowed port ranges
|
globalPortRanges: Array<{ from: number; to: number }>; // Global allowed port ranges
|
||||||
forwardAllGlobalRanges?: boolean; // When true, forwards all connections on global port ranges to the global targetIP
|
forwardAllGlobalRanges?: boolean; // When true, forwards all connections on global port ranges to the global targetIP
|
||||||
|
|
||||||
@ -44,9 +45,7 @@ export interface IPortProxySettings extends plugins.tls.TlsOptions {
|
|||||||
keepAliveInitialDelay?: number; // Initial delay before sending keepalive probes (ms)
|
keepAliveInitialDelay?: number; // Initial delay before sending keepalive probes (ms)
|
||||||
maxPendingDataSize?: number; // Maximum bytes to buffer during connection setup
|
maxPendingDataSize?: number; // Maximum bytes to buffer during connection setup
|
||||||
|
|
||||||
// Enhanced features
|
// Logging settings
|
||||||
disableInactivityCheck?: boolean; // Disable inactivity checking entirely
|
|
||||||
enableKeepAliveProbes?: boolean; // Enable TCP keep-alive probes
|
|
||||||
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
|
||||||
@ -55,12 +54,7 @@ export interface IPortProxySettings extends plugins.tls.TlsOptions {
|
|||||||
maxConnectionsPerIP?: number; // Maximum simultaneous connections from a single IP
|
maxConnectionsPerIP?: number; // Maximum simultaneous connections from a single IP
|
||||||
connectionRateLimitPerMinute?: number; // Max new connections per minute from a single IP
|
connectionRateLimitPerMinute?: number; // Max new connections per minute from a single IP
|
||||||
|
|
||||||
// Enhanced keep-alive settings
|
// NetworkProxy integration
|
||||||
keepAliveTreatment?: 'standard' | 'extended' | 'immortal'; // How to treat keep-alive connections
|
|
||||||
keepAliveInactivityMultiplier?: number; // Multiplier for inactivity timeout for keep-alive connections
|
|
||||||
extendedKeepAliveLifetime?: number; // Extended lifetime for keep-alive connections (ms)
|
|
||||||
|
|
||||||
// New property for NetworkProxy integration
|
|
||||||
networkProxies?: NetworkProxy[]; // Array of NetworkProxy instances to use for TLS termination
|
networkProxies?: NetworkProxy[]; // Array of NetworkProxy instances to use for TLS termination
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -100,6 +94,10 @@ interface IConnectionRecord {
|
|||||||
// New field for NetworkProxy tracking
|
// New field for NetworkProxy tracking
|
||||||
usingNetworkProxy?: boolean; // Whether this connection is using a NetworkProxy
|
usingNetworkProxy?: boolean; // Whether this connection is using a NetworkProxy
|
||||||
networkProxyIndex?: number; // Which NetworkProxy instance is being used
|
networkProxyIndex?: number; // Which NetworkProxy instance is being used
|
||||||
|
|
||||||
|
// Sleep detection fields
|
||||||
|
possibleSystemSleep?: boolean; // Flag to indicate a possible system sleep was detected
|
||||||
|
lastSleepDetection?: number; // Timestamp of the last sleep detection
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -328,7 +326,22 @@ const randomizeTimeout = (baseTimeout: number, variationPercent: number = 5): nu
|
|||||||
|
|
||||||
export class PortProxy {
|
export class PortProxy {
|
||||||
private netServers: plugins.net.Server[] = [];
|
private netServers: plugins.net.Server[] = [];
|
||||||
settings: IPortProxySettings;
|
|
||||||
|
// Define the internal settings interface to include all fields, including those removed from the public interface
|
||||||
|
settings: IPortProxySettings & {
|
||||||
|
// Internal fields removed from public interface in 3.31.0+
|
||||||
|
initialDataTimeout: number;
|
||||||
|
socketTimeout: number;
|
||||||
|
inactivityCheckInterval: number;
|
||||||
|
maxConnectionLifetime: number;
|
||||||
|
inactivityTimeout: number;
|
||||||
|
disableInactivityCheck: boolean;
|
||||||
|
enableKeepAliveProbes: boolean;
|
||||||
|
keepAliveTreatment: 'standard' | 'extended' | 'immortal';
|
||||||
|
keepAliveInactivityMultiplier: number;
|
||||||
|
extendedKeepAliveLifetime: number;
|
||||||
|
};
|
||||||
|
|
||||||
private connectionRecords: Map<string, IConnectionRecord> = new Map();
|
private connectionRecords: Map<string, IConnectionRecord> = new Map();
|
||||||
private connectionLogger: NodeJS.Timeout | null = null;
|
private connectionLogger: NodeJS.Timeout | null = null;
|
||||||
private isShuttingDown: boolean = false;
|
private isShuttingDown: boolean = false;
|
||||||
@ -353,42 +366,41 @@ export class PortProxy {
|
|||||||
private networkProxies: NetworkProxy[] = [];
|
private networkProxies: NetworkProxy[] = [];
|
||||||
|
|
||||||
constructor(settingsArg: IPortProxySettings) {
|
constructor(settingsArg: IPortProxySettings) {
|
||||||
// Set reasonable defaults for all settings
|
// Set hardcoded sensible defaults for all settings
|
||||||
this.settings = {
|
this.settings = {
|
||||||
...settingsArg,
|
...settingsArg,
|
||||||
targetIP: settingsArg.targetIP || 'localhost',
|
targetIP: settingsArg.targetIP || 'localhost',
|
||||||
|
|
||||||
// Timeout settings with reasonable defaults
|
// Hardcoded timeout settings optimized for TLS safety in all deployment scenarios
|
||||||
initialDataTimeout: settingsArg.initialDataTimeout || 60000, // 60 seconds for initial handshake
|
initialDataTimeout: 60000, // 60 seconds for initial handshake
|
||||||
socketTimeout: ensureSafeTimeout(settingsArg.socketTimeout || 3600000), // 1 hour socket timeout
|
socketTimeout: 1800000, // 30 minutes - short enough for regular certificate refresh
|
||||||
inactivityCheckInterval: settingsArg.inactivityCheckInterval || 60000, // 60 seconds interval
|
inactivityCheckInterval: 60000, // 60 seconds interval for regular cleanup
|
||||||
maxConnectionLifetime: ensureSafeTimeout(settingsArg.maxConnectionLifetime || 86400000), // 24 hours default
|
maxConnectionLifetime: 3600000, // 1 hour maximum lifetime for all connections
|
||||||
inactivityTimeout: ensureSafeTimeout(settingsArg.inactivityTimeout || 14400000), // 4 hours inactivity timeout
|
inactivityTimeout: 1800000, // 30 minutes inactivity timeout
|
||||||
|
|
||||||
gracefulShutdownTimeout: settingsArg.gracefulShutdownTimeout || 30000, // 30 seconds
|
gracefulShutdownTimeout: settingsArg.gracefulShutdownTimeout || 30000, // 30 seconds
|
||||||
|
|
||||||
// Socket optimization settings
|
// Socket optimization settings
|
||||||
noDelay: settingsArg.noDelay !== undefined ? settingsArg.noDelay : true,
|
noDelay: settingsArg.noDelay !== undefined ? settingsArg.noDelay : true,
|
||||||
keepAlive: settingsArg.keepAlive !== undefined ? settingsArg.keepAlive : true,
|
keepAlive: settingsArg.keepAlive !== undefined ? settingsArg.keepAlive : true,
|
||||||
keepAliveInitialDelay: settingsArg.keepAliveInitialDelay || 10000, // 10 seconds (reduced for responsiveness)
|
keepAliveInitialDelay: settingsArg.keepAliveInitialDelay || 10000, // 10 seconds
|
||||||
maxPendingDataSize: settingsArg.maxPendingDataSize || 10 * 1024 * 1024, // 10MB to handle large TLS handshakes
|
maxPendingDataSize: settingsArg.maxPendingDataSize || 10 * 1024 * 1024, // 10MB to handle large TLS handshakes
|
||||||
|
|
||||||
// Feature flags
|
// Feature flags - simplified with sensible defaults
|
||||||
disableInactivityCheck: settingsArg.disableInactivityCheck || false,
|
disableInactivityCheck: false, // Always enable inactivity checks for TLS safety
|
||||||
enableKeepAliveProbes: settingsArg.enableKeepAliveProbes !== undefined
|
enableKeepAliveProbes: true, // Always enable keep-alive probes for connection health
|
||||||
? settingsArg.enableKeepAliveProbes : true, // Enable by default
|
|
||||||
enableDetailedLogging: settingsArg.enableDetailedLogging || false,
|
enableDetailedLogging: settingsArg.enableDetailedLogging || false,
|
||||||
enableTlsDebugLogging: settingsArg.enableTlsDebugLogging || false,
|
enableTlsDebugLogging: settingsArg.enableTlsDebugLogging || false,
|
||||||
enableRandomizedTimeouts: settingsArg.enableRandomizedTimeouts || false, // Disable randomization by default
|
enableRandomizedTimeouts: settingsArg.enableRandomizedTimeouts || false,
|
||||||
|
|
||||||
// Rate limiting defaults
|
// Rate limiting defaults
|
||||||
maxConnectionsPerIP: settingsArg.maxConnectionsPerIP || 100, // 100 connections per IP
|
maxConnectionsPerIP: settingsArg.maxConnectionsPerIP || 100, // 100 connections per IP
|
||||||
connectionRateLimitPerMinute: settingsArg.connectionRateLimitPerMinute || 300, // 300 per minute
|
connectionRateLimitPerMinute: settingsArg.connectionRateLimitPerMinute || 300, // 300 per minute
|
||||||
|
|
||||||
// Enhanced keep-alive settings
|
// Keep-alive settings with sensible defaults that ensure certificate safety
|
||||||
keepAliveTreatment: settingsArg.keepAliveTreatment || 'extended', // Extended by default
|
keepAliveTreatment: 'standard', // Always use standard treatment for certificate safety
|
||||||
keepAliveInactivityMultiplier: settingsArg.keepAliveInactivityMultiplier || 6, // 6x normal inactivity timeout
|
keepAliveInactivityMultiplier: 2, // 2x normal inactivity timeout for minimal extension
|
||||||
extendedKeepAliveLifetime: settingsArg.extendedKeepAliveLifetime || 7 * 24 * 60 * 60 * 1000, // 7 days
|
extendedKeepAliveLifetime: 3 * 60 * 60 * 1000, // 3 hours maximum (previously was 7 days!)
|
||||||
};
|
};
|
||||||
|
|
||||||
// Store NetworkProxy instances if provided
|
// Store NetworkProxy instances if provided
|
||||||
@ -413,15 +425,23 @@ export class PortProxy {
|
|||||||
serverName?: string
|
serverName?: string
|
||||||
): void {
|
): void {
|
||||||
// Determine which NetworkProxy to use
|
// Determine which NetworkProxy to use
|
||||||
const proxyIndex = domainConfig.networkProxyIndex !== undefined
|
const proxyIndex =
|
||||||
? domainConfig.networkProxyIndex
|
domainConfig.networkProxyIndex !== undefined ? domainConfig.networkProxyIndex : 0;
|
||||||
: 0;
|
|
||||||
|
|
||||||
// Validate the NetworkProxy index
|
// Validate the NetworkProxy index
|
||||||
if (proxyIndex < 0 || proxyIndex >= this.networkProxies.length) {
|
if (proxyIndex < 0 || proxyIndex >= this.networkProxies.length) {
|
||||||
console.log(`[${connectionId}] Invalid NetworkProxy index: ${proxyIndex}. Using fallback direct connection.`);
|
console.log(
|
||||||
|
`[${connectionId}] Invalid NetworkProxy index: ${proxyIndex}. Using fallback direct connection.`
|
||||||
|
);
|
||||||
// Fall back to direct connection
|
// Fall back to direct connection
|
||||||
return this.setupDirectConnection(connectionId, socket, record, domainConfig, serverName, initialData);
|
return this.setupDirectConnection(
|
||||||
|
connectionId,
|
||||||
|
socket,
|
||||||
|
record,
|
||||||
|
domainConfig,
|
||||||
|
serverName,
|
||||||
|
initialData
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const networkProxy = this.networkProxies[proxyIndex];
|
const networkProxy = this.networkProxies[proxyIndex];
|
||||||
@ -437,7 +457,7 @@ export class PortProxy {
|
|||||||
// Create a connection to the NetworkProxy
|
// Create a connection to the NetworkProxy
|
||||||
const proxySocket = plugins.net.connect({
|
const proxySocket = plugins.net.connect({
|
||||||
host: proxyHost,
|
host: proxyHost,
|
||||||
port: proxyPort
|
port: proxyPort,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Store the outgoing socket in the record
|
// Store the outgoing socket in the record
|
||||||
@ -475,13 +495,34 @@ export class PortProxy {
|
|||||||
|
|
||||||
socket.on('close', () => {
|
socket.on('close', () => {
|
||||||
if (this.settings.enableDetailedLogging) {
|
if (this.settings.enableDetailedLogging) {
|
||||||
console.log(`[${connectionId}] Client connection closed after forwarding to NetworkProxy`);
|
console.log(
|
||||||
|
`[${connectionId}] Client connection closed after forwarding to NetworkProxy`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
this.cleanupConnection(record, 'client_closed');
|
this.cleanupConnection(record, 'client_closed');
|
||||||
});
|
});
|
||||||
|
|
||||||
// Update activity on data transfer
|
// Update activity on data transfer
|
||||||
socket.on('data', () => this.updateActivity(record));
|
socket.on('data', (chunk: Buffer) => {
|
||||||
|
this.updateActivity(record);
|
||||||
|
|
||||||
|
// Check for potential TLS renegotiation or reconnection packets
|
||||||
|
if (chunk.length > 0 && chunk[0] === 22) {
|
||||||
|
// ContentType.handshake
|
||||||
|
if (this.settings.enableDetailedLogging) {
|
||||||
|
console.log(
|
||||||
|
`[${connectionId}] Detected potential TLS handshake data while connected to NetworkProxy`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: We don't need to explicitly forward the renegotiation packets
|
||||||
|
// because socket.pipe(proxySocket) is already handling that.
|
||||||
|
// The pipe ensures all data (including renegotiation) flows through properly.
|
||||||
|
// Just update the activity timestamp to prevent timeouts
|
||||||
|
record.lastActivity = Date.now();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
proxySocket.on('data', () => this.updateActivity(record));
|
proxySocket.on('data', () => this.updateActivity(record));
|
||||||
|
|
||||||
if (this.settings.enableDetailedLogging) {
|
if (this.settings.enableDetailedLogging) {
|
||||||
@ -585,7 +626,9 @@ export class PortProxy {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
// Ignore errors - these are optional enhancements
|
// Ignore errors - these are optional enhancements
|
||||||
if (this.settings.enableDetailedLogging) {
|
if (this.settings.enableDetailedLogging) {
|
||||||
console.log(`[${connectionId}] Enhanced TCP keep-alive not supported for outgoing socket: ${err}`);
|
console.log(
|
||||||
|
`[${connectionId}] Enhanced TCP keep-alive not supported for outgoing socket: ${err}`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -642,7 +685,9 @@ export class PortProxy {
|
|||||||
// For keep-alive connections, just log a warning instead of closing
|
// For keep-alive connections, just log a warning instead of closing
|
||||||
if (record.hasKeepAlive) {
|
if (record.hasKeepAlive) {
|
||||||
console.log(
|
console.log(
|
||||||
`[${connectionId}] Timeout event on incoming keep-alive connection from ${record.remoteIP} after ${plugins.prettyMs(
|
`[${connectionId}] Timeout event on incoming keep-alive connection from ${
|
||||||
|
record.remoteIP
|
||||||
|
} after ${plugins.prettyMs(
|
||||||
this.settings.socketTimeout || 3600000
|
this.settings.socketTimeout || 3600000
|
||||||
)}. Connection preserved.`
|
)}. Connection preserved.`
|
||||||
);
|
);
|
||||||
@ -652,9 +697,9 @@ export class PortProxy {
|
|||||||
|
|
||||||
// For non-keep-alive connections, proceed with normal cleanup
|
// For non-keep-alive connections, proceed with normal cleanup
|
||||||
console.log(
|
console.log(
|
||||||
`[${connectionId}] Timeout on incoming side from ${record.remoteIP} after ${plugins.prettyMs(
|
`[${connectionId}] Timeout on incoming side from ${
|
||||||
this.settings.socketTimeout || 3600000
|
record.remoteIP
|
||||||
)}`
|
} after ${plugins.prettyMs(this.settings.socketTimeout || 3600000)}`
|
||||||
);
|
);
|
||||||
if (record.incomingTerminationReason === null) {
|
if (record.incomingTerminationReason === null) {
|
||||||
record.incomingTerminationReason = 'timeout';
|
record.incomingTerminationReason = 'timeout';
|
||||||
@ -667,7 +712,9 @@ export class PortProxy {
|
|||||||
// For keep-alive connections, just log a warning instead of closing
|
// For keep-alive connections, just log a warning instead of closing
|
||||||
if (record.hasKeepAlive) {
|
if (record.hasKeepAlive) {
|
||||||
console.log(
|
console.log(
|
||||||
`[${connectionId}] Timeout event on outgoing keep-alive connection from ${record.remoteIP} after ${plugins.prettyMs(
|
`[${connectionId}] Timeout event on outgoing keep-alive connection from ${
|
||||||
|
record.remoteIP
|
||||||
|
} after ${plugins.prettyMs(
|
||||||
this.settings.socketTimeout || 3600000
|
this.settings.socketTimeout || 3600000
|
||||||
)}. Connection preserved.`
|
)}. Connection preserved.`
|
||||||
);
|
);
|
||||||
@ -677,9 +724,9 @@ export class PortProxy {
|
|||||||
|
|
||||||
// For non-keep-alive connections, proceed with normal cleanup
|
// For non-keep-alive connections, proceed with normal cleanup
|
||||||
console.log(
|
console.log(
|
||||||
`[${connectionId}] Timeout on outgoing side from ${record.remoteIP} after ${plugins.prettyMs(
|
`[${connectionId}] Timeout on outgoing side from ${
|
||||||
this.settings.socketTimeout || 3600000
|
record.remoteIP
|
||||||
)}`
|
} after ${plugins.prettyMs(this.settings.socketTimeout || 3600000)}`
|
||||||
);
|
);
|
||||||
if (record.outgoingTerminationReason === null) {
|
if (record.outgoingTerminationReason === null) {
|
||||||
record.outgoingTerminationReason = 'timeout';
|
record.outgoingTerminationReason = 'timeout';
|
||||||
@ -695,7 +742,9 @@ export class PortProxy {
|
|||||||
targetSocket.setTimeout(0);
|
targetSocket.setTimeout(0);
|
||||||
|
|
||||||
if (this.settings.enableDetailedLogging) {
|
if (this.settings.enableDetailedLogging) {
|
||||||
console.log(`[${connectionId}] Disabled socket timeouts for immortal keep-alive connection`);
|
console.log(
|
||||||
|
`[${connectionId}] Disabled socket timeouts for immortal keep-alive connection`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Set normal timeouts for other connections
|
// Set normal timeouts for other connections
|
||||||
@ -725,9 +774,7 @@ export class PortProxy {
|
|||||||
const combinedData = Buffer.concat(record.pendingData);
|
const combinedData = Buffer.concat(record.pendingData);
|
||||||
targetSocket.write(combinedData, (err) => {
|
targetSocket.write(combinedData, (err) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
console.log(
|
console.log(`[${connectionId}] Error writing pending data to target: ${err.message}`);
|
||||||
`[${connectionId}] Error writing pending data to target: ${err.message}`
|
|
||||||
);
|
|
||||||
return this.initiateCleanupOnce(record, 'write_error');
|
return this.initiateCleanupOnce(record, 'write_error');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -746,7 +793,9 @@ export class PortProxy {
|
|||||||
? ` (Port-based for domain: ${domainConfig.domains.join(', ')})`
|
? ` (Port-based for domain: ${domainConfig.domains.join(', ')})`
|
||||||
: ''
|
: ''
|
||||||
}` +
|
}` +
|
||||||
` TLS: ${record.isTLS ? 'Yes' : 'No'}, Keep-Alive: ${record.hasKeepAlive ? 'Yes' : 'No'}`
|
` TLS: ${record.isTLS ? 'Yes' : 'No'}, Keep-Alive: ${
|
||||||
|
record.hasKeepAlive ? 'Yes' : 'No'
|
||||||
|
}`
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
console.log(
|
console.log(
|
||||||
@ -777,7 +826,9 @@ export class PortProxy {
|
|||||||
? ` (Port-based for domain: ${domainConfig.domains.join(', ')})`
|
? ` (Port-based for domain: ${domainConfig.domains.join(', ')})`
|
||||||
: ''
|
: ''
|
||||||
}` +
|
}` +
|
||||||
` TLS: ${record.isTLS ? 'Yes' : 'No'}, Keep-Alive: ${record.hasKeepAlive ? 'Yes' : 'No'}`
|
` TLS: ${record.isTLS ? 'Yes' : 'No'}, Keep-Alive: ${
|
||||||
|
record.hasKeepAlive ? 'Yes' : 'No'
|
||||||
|
}`
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
console.log(
|
console.log(
|
||||||
@ -799,14 +850,66 @@ export class PortProxy {
|
|||||||
|
|
||||||
// Add the renegotiation listener for SNI validation
|
// Add the renegotiation listener for SNI validation
|
||||||
if (serverName) {
|
if (serverName) {
|
||||||
|
// This listener will check for TLS renegotiation attempts
|
||||||
|
// Note: We don't need to explicitly forward the renegotiation packets
|
||||||
|
// since socket.pipe(targetSocket) is already set up earlier and handles that
|
||||||
socket.on('data', (renegChunk: Buffer) => {
|
socket.on('data', (renegChunk: Buffer) => {
|
||||||
if (renegChunk.length > 0 && renegChunk.readUInt8(0) === 22) {
|
if (renegChunk.length > 0 && renegChunk.readUInt8(0) === 22) {
|
||||||
try {
|
try {
|
||||||
// Try to extract SNI from potential renegotiation
|
// Try to extract SNI from potential renegotiation
|
||||||
const newSNI = extractSNI(renegChunk, this.settings.enableTlsDebugLogging);
|
const newSNI = extractSNI(renegChunk, this.settings.enableTlsDebugLogging);
|
||||||
if (newSNI && newSNI !== record.lockedDomain) {
|
|
||||||
|
// IMPORTANT: If we can't extract an SNI from renegotiation, we MUST allow it through
|
||||||
|
// Otherwise valid renegotiations that don't explicitly repeat the SNI will break
|
||||||
|
if (newSNI === undefined) {
|
||||||
|
if (this.settings.enableDetailedLogging) {
|
||||||
console.log(
|
console.log(
|
||||||
`[${connectionId}] Rehandshake detected with different SNI: ${newSNI} vs locked ${record.lockedDomain}. Terminating connection.`
|
`[${connectionId}] Rehandshake detected without SNI, allowing it through.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// Let it pass through - this is critical for Chrome's TLS handling
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the SNI has changed
|
||||||
|
if (newSNI && newSNI !== record.lockedDomain) {
|
||||||
|
// Instead of immediately terminating, check if the new SNI would be allowed
|
||||||
|
// by the same ruleset that allowed the initial connection
|
||||||
|
const newDomainConfig = this.settings.domainConfigs.find((config) =>
|
||||||
|
config.domains.some((d) => plugins.minimatch(newSNI, d))
|
||||||
|
);
|
||||||
|
|
||||||
|
// If we found a matching domain config, check IP rules
|
||||||
|
if (newDomainConfig) {
|
||||||
|
const effectiveAllowedIPs = [
|
||||||
|
...newDomainConfig.allowedIPs,
|
||||||
|
...(this.settings.defaultAllowedIPs || []),
|
||||||
|
];
|
||||||
|
const effectiveBlockedIPs = [
|
||||||
|
...(newDomainConfig.blockedIPs || []),
|
||||||
|
...(this.settings.defaultBlockedIPs || []),
|
||||||
|
];
|
||||||
|
|
||||||
|
// Check if the IP is allowed for the new domain
|
||||||
|
if (isGlobIPAllowed(record.remoteIP, effectiveAllowedIPs, effectiveBlockedIPs)) {
|
||||||
|
// Allow the domain switch - Chrome is reusing the connection for a different domain
|
||||||
|
if (this.settings.enableDetailedLogging) {
|
||||||
|
console.log(
|
||||||
|
`[${connectionId}] Rehandshake with new SNI: ${newSNI} (previously ${record.lockedDomain}). ` +
|
||||||
|
`New domain is allowed by rules, permitting connection reuse.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the locked domain to the new domain
|
||||||
|
record.lockedDomain = newSNI;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we get here, either no matching domain config was found or the IP is not allowed
|
||||||
|
console.log(
|
||||||
|
`[${connectionId}] Rehandshake detected with different SNI: ${newSNI} vs locked ${record.lockedDomain}. ` +
|
||||||
|
`New domain not allowed by rules. Terminating connection.`
|
||||||
);
|
);
|
||||||
this.initiateCleanupOnce(record, 'sni_mismatch');
|
this.initiateCleanupOnce(record, 'sni_mismatch');
|
||||||
} else if (newSNI && this.settings.enableDetailedLogging) {
|
} else if (newSNI && this.settings.enableDetailedLogging) {
|
||||||
@ -815,6 +918,8 @@ export class PortProxy {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
// Always allow the renegotiation to continue if we encounter an error
|
||||||
|
// This ensures Chrome can complete its TLS renegotiation
|
||||||
console.log(
|
console.log(
|
||||||
`[${connectionId}] Error processing potential renegotiation: ${err}. Allowing connection to continue.`
|
`[${connectionId}] Error processing potential renegotiation: ${err}. Allowing connection to continue.`
|
||||||
);
|
);
|
||||||
@ -831,10 +936,44 @@ export class PortProxy {
|
|||||||
// For immortal keep-alive connections, skip setting a timeout completely
|
// For immortal keep-alive connections, skip setting a timeout completely
|
||||||
if (record.hasKeepAlive && this.settings.keepAliveTreatment === 'immortal') {
|
if (record.hasKeepAlive && this.settings.keepAliveTreatment === 'immortal') {
|
||||||
if (this.settings.enableDetailedLogging) {
|
if (this.settings.enableDetailedLogging) {
|
||||||
console.log(`[${connectionId}] Keep-alive connection with immortal treatment - no max lifetime`);
|
console.log(
|
||||||
|
`[${connectionId}] Keep-alive connection with immortal treatment - no max lifetime`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
// No cleanup timer for immortal connections
|
// No cleanup timer for immortal connections
|
||||||
}
|
}
|
||||||
|
// For TLS keep-alive connections, use a more generous timeout now that
|
||||||
|
// we've fixed the renegotiation handling issue that was causing certificate problems
|
||||||
|
else if (record.hasKeepAlive && record.isTLS) {
|
||||||
|
// Use a longer timeout for TLS connections now that renegotiation handling is fixed
|
||||||
|
// This reduces unnecessary reconnections while still ensuring certificate freshness
|
||||||
|
const tlsKeepAliveTimeout = 4 * 60 * 60 * 1000; // 4 hours for TLS keep-alive - increased from 30 minutes
|
||||||
|
const safeTimeout = ensureSafeTimeout(tlsKeepAliveTimeout);
|
||||||
|
|
||||||
|
record.cleanupTimer = setTimeout(() => {
|
||||||
|
console.log(
|
||||||
|
`[${connectionId}] TLS keep-alive connection from ${
|
||||||
|
record.remoteIP
|
||||||
|
} exceeded max lifetime (${plugins.prettyMs(
|
||||||
|
tlsKeepAliveTimeout
|
||||||
|
)}), forcing cleanup to refresh certificate context.`
|
||||||
|
);
|
||||||
|
this.initiateCleanupOnce(record, 'tls_certificate_refresh');
|
||||||
|
}, safeTimeout);
|
||||||
|
|
||||||
|
// Make sure timeout doesn't keep the process alive
|
||||||
|
if (record.cleanupTimer.unref) {
|
||||||
|
record.cleanupTimer.unref();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.settings.enableDetailedLogging) {
|
||||||
|
console.log(
|
||||||
|
`[${connectionId}] TLS keep-alive connection with aggressive certificate refresh protection, lifetime: ${plugins.prettyMs(
|
||||||
|
tlsKeepAliveTimeout
|
||||||
|
)}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
// For extended keep-alive connections, use extended timeout
|
// For extended keep-alive connections, use extended timeout
|
||||||
else if (record.hasKeepAlive && this.settings.keepAliveTreatment === 'extended') {
|
else if (record.hasKeepAlive && this.settings.keepAliveTreatment === 'extended') {
|
||||||
const extendedTimeout = this.settings.extendedKeepAliveLifetime || 7 * 24 * 60 * 60 * 1000; // 7 days
|
const extendedTimeout = this.settings.extendedKeepAliveLifetime || 7 * 24 * 60 * 60 * 1000; // 7 days
|
||||||
@ -842,9 +981,9 @@ export class PortProxy {
|
|||||||
|
|
||||||
record.cleanupTimer = setTimeout(() => {
|
record.cleanupTimer = setTimeout(() => {
|
||||||
console.log(
|
console.log(
|
||||||
`[${connectionId}] Keep-alive connection from ${record.remoteIP} exceeded extended lifetime (${plugins.prettyMs(
|
`[${connectionId}] Keep-alive connection from ${
|
||||||
extendedTimeout
|
record.remoteIP
|
||||||
)}), forcing cleanup.`
|
} exceeded extended lifetime (${plugins.prettyMs(extendedTimeout)}), forcing cleanup.`
|
||||||
);
|
);
|
||||||
this.initiateCleanupOnce(record, 'extended_lifetime');
|
this.initiateCleanupOnce(record, 'extended_lifetime');
|
||||||
}, safeTimeout);
|
}, safeTimeout);
|
||||||
@ -855,20 +994,25 @@ export class PortProxy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.settings.enableDetailedLogging) {
|
if (this.settings.enableDetailedLogging) {
|
||||||
console.log(`[${connectionId}] Keep-alive connection with extended lifetime of ${plugins.prettyMs(extendedTimeout)}`);
|
console.log(
|
||||||
|
`[${connectionId}] Keep-alive connection with extended lifetime of ${plugins.prettyMs(
|
||||||
|
extendedTimeout
|
||||||
|
)}`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// For standard connections, use normal timeout
|
// For standard connections, use normal timeout
|
||||||
else {
|
else {
|
||||||
// Use domain-specific timeout if available, otherwise use default
|
// Use domain-specific timeout if available, otherwise use default
|
||||||
const connectionTimeout = record.domainConfig?.connectionTimeout || this.settings.maxConnectionLifetime!;
|
const connectionTimeout =
|
||||||
|
record.domainConfig?.connectionTimeout || this.settings.maxConnectionLifetime!;
|
||||||
const safeTimeout = ensureSafeTimeout(connectionTimeout);
|
const safeTimeout = ensureSafeTimeout(connectionTimeout);
|
||||||
|
|
||||||
record.cleanupTimer = setTimeout(() => {
|
record.cleanupTimer = setTimeout(() => {
|
||||||
console.log(
|
console.log(
|
||||||
`[${connectionId}] Connection from ${record.remoteIP} exceeded max lifetime (${plugins.prettyMs(
|
`[${connectionId}] Connection from ${
|
||||||
connectionTimeout
|
record.remoteIP
|
||||||
)}), forcing cleanup.`
|
} exceeded max lifetime (${plugins.prettyMs(connectionTimeout)}), forcing cleanup.`
|
||||||
);
|
);
|
||||||
this.initiateCleanupOnce(record, 'connection_timeout');
|
this.initiateCleanupOnce(record, 'connection_timeout');
|
||||||
}, safeTimeout);
|
}, safeTimeout);
|
||||||
@ -950,6 +1094,126 @@ export class PortProxy {
|
|||||||
this.terminationStats[side][reason] = (this.terminationStats[side][reason] || 0) + 1;
|
this.terminationStats[side][reason] = (this.terminationStats[side][reason] || 0) + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update connection activity timestamp with sleep detection
|
||||||
|
*/
|
||||||
|
private updateActivity(record: IConnectionRecord): void {
|
||||||
|
// Get the current time
|
||||||
|
const now = Date.now();
|
||||||
|
|
||||||
|
// Check if there was a large time gap that suggests system sleep
|
||||||
|
if (record.lastActivity > 0) {
|
||||||
|
const timeDiff = now - record.lastActivity;
|
||||||
|
|
||||||
|
// If time difference is very large (> 30 minutes) and this is a keep-alive connection,
|
||||||
|
// this might indicate system sleep rather than just inactivity
|
||||||
|
if (timeDiff > 30 * 60 * 1000 && record.hasKeepAlive) {
|
||||||
|
if (this.settings.enableDetailedLogging) {
|
||||||
|
console.log(
|
||||||
|
`[${record.id}] Detected possible system sleep for ${plugins.prettyMs(timeDiff)}. ` +
|
||||||
|
`Handling keep-alive connection after long inactivity.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// For TLS keep-alive connections after sleep/long inactivity, force close
|
||||||
|
// to make browser establish a new connection with fresh certificate context
|
||||||
|
if (record.isTLS && record.tlsHandshakeComplete) {
|
||||||
|
// More generous timeout now that we've fixed the renegotiation handling
|
||||||
|
if (timeDiff > 2 * 60 * 60 * 1000) {
|
||||||
|
// If inactive for more than 2 hours (increased from 20 minutes)
|
||||||
|
console.log(
|
||||||
|
`[${record.id}] TLS connection inactive for ${plugins.prettyMs(timeDiff)}. ` +
|
||||||
|
`Closing to force new connection with fresh certificate.`
|
||||||
|
);
|
||||||
|
return this.initiateCleanupOnce(record, 'certificate_refresh_needed');
|
||||||
|
} else if (timeDiff > 30 * 60 * 1000) {
|
||||||
|
// For shorter but still significant inactivity (30+ minutes), refresh TLS state
|
||||||
|
console.log(
|
||||||
|
`[${record.id}] TLS connection inactive for ${plugins.prettyMs(timeDiff)}. ` +
|
||||||
|
`Refreshing TLS state.`
|
||||||
|
);
|
||||||
|
this.refreshTlsStateAfterSleep(record);
|
||||||
|
|
||||||
|
// Add an additional check in 15 minutes if no activity
|
||||||
|
const refreshCheckId = record.id;
|
||||||
|
const refreshCheck = setTimeout(() => {
|
||||||
|
const currentRecord = this.connectionRecords.get(refreshCheckId);
|
||||||
|
if (currentRecord && Date.now() - currentRecord.lastActivity > 15 * 60 * 1000) {
|
||||||
|
console.log(
|
||||||
|
`[${refreshCheckId}] No activity detected after TLS refresh. ` +
|
||||||
|
`Closing connection to ensure certificate freshness.`
|
||||||
|
);
|
||||||
|
this.initiateCleanupOnce(currentRecord, 'tls_refresh_verification_failed');
|
||||||
|
}
|
||||||
|
}, 15 * 60 * 1000);
|
||||||
|
|
||||||
|
// Make sure timeout doesn't keep the process alive
|
||||||
|
if (refreshCheck.unref) {
|
||||||
|
refreshCheck.unref();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// For shorter inactivity periods, try to refresh the TLS state normally
|
||||||
|
this.refreshTlsStateAfterSleep(record);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark that we detected sleep
|
||||||
|
record.possibleSystemSleep = true;
|
||||||
|
record.lastSleepDetection = now;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the activity timestamp
|
||||||
|
record.lastActivity = now;
|
||||||
|
|
||||||
|
// Clear any inactivity warning
|
||||||
|
if (record.inactivityWarningIssued) {
|
||||||
|
record.inactivityWarningIssued = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refresh TLS state after sleep detection
|
||||||
|
*/
|
||||||
|
private refreshTlsStateAfterSleep(record: IConnectionRecord): void {
|
||||||
|
// Skip if we're using a NetworkProxy as it handles its own TLS state
|
||||||
|
if (record.usingNetworkProxy) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// For outgoing connections that might need to be refreshed
|
||||||
|
if (record.outgoing && !record.outgoing.destroyed) {
|
||||||
|
// Check how long this connection has been established
|
||||||
|
const connectionAge = Date.now() - record.incomingStartTime;
|
||||||
|
const hourInMs = 60 * 60 * 1000;
|
||||||
|
|
||||||
|
// For TLS browser connections, use a more generous timeout now that
|
||||||
|
// we've fixed the renegotiation handling issues
|
||||||
|
if (record.isTLS && record.hasKeepAlive && connectionAge > 8 * hourInMs) { // 8 hours instead of 45 minutes
|
||||||
|
console.log(
|
||||||
|
`[${record.id}] Long-lived TLS connection (${plugins.prettyMs(connectionAge)}). ` +
|
||||||
|
`Closing to ensure proper certificate handling on browser reconnect in proxy chain.`
|
||||||
|
);
|
||||||
|
return this.initiateCleanupOnce(record, 'certificate_context_refresh');
|
||||||
|
}
|
||||||
|
|
||||||
|
// For newer connections, try to send a refresh packet
|
||||||
|
record.outgoing.write(Buffer.alloc(0));
|
||||||
|
|
||||||
|
if (this.settings.enableDetailedLogging) {
|
||||||
|
console.log(`[${record.id}] Sent refresh packet after sleep detection`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.log(`[${record.id}] Error refreshing TLS state: ${err}`);
|
||||||
|
|
||||||
|
// If we hit an error, it's likely the connection is already broken
|
||||||
|
// Force cleanup to ensure browser reconnects cleanly
|
||||||
|
return this.initiateCleanupOnce(record, 'tls_refresh_error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cleans up a connection record.
|
* Cleans up a connection record.
|
||||||
* Destroys both incoming and outgoing sockets, clears timers, and removes the record.
|
* Destroys both incoming and outgoing sockets, clears timers, and removes the record.
|
||||||
@ -1047,7 +1311,9 @@ export class PortProxy {
|
|||||||
` Duration: ${plugins.prettyMs(
|
` Duration: ${plugins.prettyMs(
|
||||||
duration
|
duration
|
||||||
)}, Bytes IN: ${bytesReceived}, OUT: ${bytesSent}, ` +
|
)}, Bytes IN: ${bytesReceived}, OUT: ${bytesSent}, ` +
|
||||||
`TLS: ${record.isTLS ? 'Yes' : 'No'}, Keep-Alive: ${record.hasKeepAlive ? 'Yes' : 'No'}` +
|
`TLS: ${record.isTLS ? 'Yes' : 'No'}, Keep-Alive: ${
|
||||||
|
record.hasKeepAlive ? 'Yes' : 'No'
|
||||||
|
}` +
|
||||||
`${record.usingNetworkProxy ? `, NetworkProxy: ${record.networkProxyIndex}` : ''}`
|
`${record.usingNetworkProxy ? `, NetworkProxy: ${record.networkProxyIndex}` : ''}`
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
@ -1058,18 +1324,6 @@ export class PortProxy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Update connection activity timestamp
|
|
||||||
*/
|
|
||||||
private updateActivity(record: IConnectionRecord): void {
|
|
||||||
record.lastActivity = Date.now();
|
|
||||||
|
|
||||||
// Clear any inactivity warning
|
|
||||||
if (record.inactivityWarningIssued) {
|
|
||||||
record.inactivityWarningIssued = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get target IP with round-robin support
|
* Get target IP with round-robin support
|
||||||
*/
|
*/
|
||||||
@ -1091,7 +1345,10 @@ export class PortProxy {
|
|||||||
console.log(`[${record.id}] Connection cleanup initiated for ${record.remoteIP} (${reason})`);
|
console.log(`[${record.id}] Connection cleanup initiated for ${record.remoteIP} (${reason})`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (record.incomingTerminationReason === null || record.incomingTerminationReason === undefined) {
|
if (
|
||||||
|
record.incomingTerminationReason === null ||
|
||||||
|
record.incomingTerminationReason === undefined
|
||||||
|
) {
|
||||||
record.incomingTerminationReason = reason;
|
record.incomingTerminationReason = reason;
|
||||||
this.incrementTerminationStat('incoming', reason);
|
this.incrementTerminationStat('incoming', reason);
|
||||||
}
|
}
|
||||||
@ -1245,7 +1502,10 @@ export class PortProxy {
|
|||||||
outgoingTerminationReason: null,
|
outgoingTerminationReason: null,
|
||||||
|
|
||||||
// Initialize NetworkProxy tracking fields
|
// Initialize NetworkProxy tracking fields
|
||||||
usingNetworkProxy: false
|
usingNetworkProxy: false,
|
||||||
|
|
||||||
|
// Initialize sleep detection fields
|
||||||
|
possibleSystemSleep: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Apply keep-alive settings if enabled
|
// Apply keep-alive settings if enabled
|
||||||
@ -1266,7 +1526,9 @@ export class PortProxy {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
// Ignore errors - these are optional enhancements
|
// Ignore errors - these are optional enhancements
|
||||||
if (this.settings.enableDetailedLogging) {
|
if (this.settings.enableDetailedLogging) {
|
||||||
console.log(`[${connectionId}] Enhanced TCP keep-alive settings not supported: ${err}`);
|
console.log(
|
||||||
|
`[${connectionId}] Enhanced TCP keep-alive settings not supported: ${err}`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1706,11 +1968,54 @@ export class PortProxy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Skip inactivity check if disabled or for immortal keep-alive connections
|
// Skip inactivity check if disabled or for immortal keep-alive connections
|
||||||
if (!this.settings.disableInactivityCheck &&
|
if (
|
||||||
!(record.hasKeepAlive && this.settings.keepAliveTreatment === 'immortal')) {
|
!this.settings.disableInactivityCheck &&
|
||||||
|
!(record.hasKeepAlive && this.settings.keepAliveTreatment === 'immortal')
|
||||||
|
) {
|
||||||
const inactivityTime = now - record.lastActivity;
|
const inactivityTime = now - record.lastActivity;
|
||||||
|
|
||||||
|
// Special handling for TLS keep-alive connections
|
||||||
|
if (
|
||||||
|
record.hasKeepAlive &&
|
||||||
|
record.isTLS &&
|
||||||
|
inactivityTime > this.settings.inactivityTimeout! / 2
|
||||||
|
) {
|
||||||
|
// For TLS keep-alive connections that are getting stale, try to refresh before closing
|
||||||
|
if (!record.inactivityWarningIssued) {
|
||||||
|
console.log(
|
||||||
|
`[${id}] TLS keep-alive connection from ${
|
||||||
|
record.remoteIP
|
||||||
|
} inactive for ${plugins.prettyMs(inactivityTime)}. ` +
|
||||||
|
`Attempting to preserve connection.`
|
||||||
|
);
|
||||||
|
|
||||||
|
// Set warning flag but give a much longer grace period for TLS connections
|
||||||
|
record.inactivityWarningIssued = true;
|
||||||
|
|
||||||
|
// For TLS connections, extend the last activity time considerably
|
||||||
|
// This gives browsers more time to re-establish the connection properly
|
||||||
|
record.lastActivity = now - this.settings.inactivityTimeout! / 3;
|
||||||
|
|
||||||
|
// Try to stimulate the connection with a probe packet
|
||||||
|
if (record.outgoing && !record.outgoing.destroyed) {
|
||||||
|
try {
|
||||||
|
// For TLS connections, send a proper TLS heartbeat-like packet
|
||||||
|
// This is just a small empty buffer that won't affect the TLS session
|
||||||
|
record.outgoing.write(Buffer.alloc(0));
|
||||||
|
|
||||||
|
if (this.settings.enableDetailedLogging) {
|
||||||
|
console.log(`[${id}] Sent TLS keep-alive probe packet`);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.log(`[${id}] Error sending TLS probe packet: ${err}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't proceed to the normal inactivity check logic
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Use extended timeout for extended-treatment keep-alive connections
|
// Use extended timeout for extended-treatment keep-alive connections
|
||||||
let effectiveTimeout = this.settings.inactivityTimeout!;
|
let effectiveTimeout = this.settings.inactivityTimeout!;
|
||||||
if (record.hasKeepAlive && this.settings.keepAliveTreatment === 'extended') {
|
if (record.hasKeepAlive && this.settings.keepAliveTreatment === 'extended') {
|
||||||
@ -1722,7 +2027,9 @@ export class PortProxy {
|
|||||||
// For keep-alive connections, issue a warning first
|
// For keep-alive connections, issue a warning first
|
||||||
if (record.hasKeepAlive && !record.inactivityWarningIssued) {
|
if (record.hasKeepAlive && !record.inactivityWarningIssued) {
|
||||||
console.log(
|
console.log(
|
||||||
`[${id}] Warning: Keep-alive connection from ${record.remoteIP} inactive for ${plugins.prettyMs(inactivityTime)}. ` +
|
`[${id}] Warning: Keep-alive connection from ${
|
||||||
|
record.remoteIP
|
||||||
|
} inactive for ${plugins.prettyMs(inactivityTime)}. ` +
|
||||||
`Will close in 10 minutes if no activity.`
|
`Will close in 10 minutes if no activity.`
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -1742,6 +2049,33 @@ export class PortProxy {
|
|||||||
console.log(`[${id}] Error sending probe packet: ${err}`);
|
console.log(`[${id}] Error sending probe packet: ${err}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// MODIFIED: For TLS connections, be more lenient before closing
|
||||||
|
// For TLS browser connections, we need to handle certificate context properly
|
||||||
|
if (record.isTLS && record.hasKeepAlive) {
|
||||||
|
// For very long inactivity, it's better to close the connection
|
||||||
|
// so the browser establishes a new one with a fresh certificate context
|
||||||
|
if (inactivityTime > 6 * 60 * 60 * 1000) {
|
||||||
|
// 6 hours
|
||||||
|
console.log(
|
||||||
|
`[${id}] TLS keep-alive connection from ${
|
||||||
|
record.remoteIP
|
||||||
|
} inactive for ${plugins.prettyMs(inactivityTime)}. ` +
|
||||||
|
`Closing to ensure proper certificate handling on browser reconnect.`
|
||||||
|
);
|
||||||
|
this.cleanupConnection(record, 'tls_certificate_refresh');
|
||||||
|
} else {
|
||||||
|
// For shorter inactivity periods, add grace period
|
||||||
|
console.log(
|
||||||
|
`[${id}] TLS keep-alive connection from ${
|
||||||
|
record.remoteIP
|
||||||
|
} inactive for ${plugins.prettyMs(inactivityTime)}. ` +
|
||||||
|
`Adding extra grace period.`
|
||||||
|
);
|
||||||
|
|
||||||
|
// Give additional time for browsers to reconnect properly
|
||||||
|
record.lastActivity = now - effectiveTimeout / 2;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// For non-keep-alive or after warning, close the connection
|
// For non-keep-alive or after warning, close the connection
|
||||||
console.log(
|
console.log(
|
||||||
@ -1751,10 +2085,13 @@ export class PortProxy {
|
|||||||
);
|
);
|
||||||
this.cleanupConnection(record, 'inactivity');
|
this.cleanupConnection(record, 'inactivity');
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} else if (inactivityTime <= effectiveTimeout && record.inactivityWarningIssued) {
|
} else if (inactivityTime <= effectiveTimeout && record.inactivityWarningIssued) {
|
||||||
// If activity detected after warning, clear the warning
|
// If activity detected after warning, clear the warning
|
||||||
if (this.settings.enableDetailedLogging) {
|
if (this.settings.enableDetailedLogging) {
|
||||||
console.log(`[${id}] Connection activity detected after inactivity warning, resetting warning`);
|
console.log(
|
||||||
|
`[${id}] Connection activity detected after inactivity warning, resetting warning`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
record.inactivityWarningIssued = false;
|
record.inactivityWarningIssued = false;
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user