@push.rocks/smartproxy 3.32.0 → 3.32.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@push.rocks/smartproxy",
3
- "version": "3.32.0",
3
+ "version": "3.32.1",
4
4
  "private": false,
5
5
  "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.",
6
6
  "main": "dist_ts/index.js",
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@push.rocks/smartproxy',
6
- version: '3.32.0',
6
+ version: '3.32.1',
7
7
  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.'
8
8
  }
@@ -100,6 +100,8 @@ interface IConnectionRecord {
100
100
 
101
101
  // Keep-alive tracking
102
102
  hasKeepAlive: boolean; // Whether keep-alive is enabled for this connection
103
+ incomingKeepAliveEnabled?: boolean; // Whether keep-alive is enabled on incoming socket
104
+ outgoingKeepAliveEnabled?: boolean; // Whether keep-alive is enabled on outgoing socket
103
105
  inactivityWarningIssued?: boolean; // Whether an inactivity warning has been issued
104
106
  incomingTerminationReason?: string | null; // Reason for incoming termination
105
107
  outgoingTerminationReason?: string | null; // Reason for outgoing termination
@@ -135,11 +137,11 @@ interface ITlsSessionCacheConfig {
135
137
  enabled: boolean; // Whether session caching is enabled
136
138
  }
137
139
 
138
- // Default configuration for session cache
140
+ // Default configuration for session cache with relaxed timeouts
139
141
  const DEFAULT_SESSION_CACHE_CONFIG: ITlsSessionCacheConfig = {
140
- maxEntries: 10000, // Default max 10,000 entries
141
- expiryTime: 24 * 60 * 60 * 1000, // 24 hours default
142
- cleanupInterval: 10 * 60 * 1000, // Clean up every 10 minutes
142
+ maxEntries: 20000, // Default max 20,000 entries (doubled)
143
+ expiryTime: 7 * 24 * 60 * 60 * 1000, // 7 days default (increased from 24 hours)
144
+ cleanupInterval: 30 * 60 * 1000, // Clean up every 30 minutes (relaxed from 10 minutes)
143
145
  enabled: true // Enabled by default
144
146
  };
145
147
 
@@ -1025,32 +1027,33 @@ export class PortProxy {
1025
1027
  }
1026
1028
 
1027
1029
  // Determine appropriate timeouts based on proxy chain position
1028
- let socketTimeout = 1800000; // 30 minutes default
1030
+ // Much more relaxed socket timeouts
1031
+ let socketTimeout = 6 * 60 * 60 * 1000; // 6 hours default for standalone
1029
1032
 
1030
1033
  if (isChainedProxy) {
1031
- // Use shorter timeouts for chained proxies to prevent certificate issues
1034
+ // Still adjust based on chain position, but with more relaxed values
1032
1035
  const chainPosition = settingsArg.chainPosition || 'middle';
1033
1036
 
1034
- // Adjust timeouts based on position in chain
1037
+ // Adjust timeouts based on position in chain, but significantly relaxed
1035
1038
  switch (chainPosition) {
1036
1039
  case 'first':
1037
- // First proxy can be a bit more lenient as it handles browser connections
1038
- socketTimeout = 1500000; // 25 minutes
1040
+ // First proxy handling browser connections
1041
+ socketTimeout = 6 * 60 * 60 * 1000; // 6 hours
1039
1042
  break;
1040
1043
  case 'middle':
1041
- // Middle proxies need shorter timeouts
1042
- socketTimeout = 1200000; // 20 minutes
1044
+ // Middle proxies
1045
+ socketTimeout = 5 * 60 * 60 * 1000; // 5 hours
1043
1046
  break;
1044
1047
  case 'last':
1045
- // Last proxy directly connects to backend
1046
- socketTimeout = 1800000; // 30 minutes
1048
+ // Last proxy connects to backend
1049
+ socketTimeout = 6 * 60 * 60 * 1000; // 6 hours
1047
1050
  break;
1048
1051
  }
1049
1052
 
1050
- console.log(`Configured as ${chainPosition} proxy in chain. Using adjusted timeouts for optimal TLS handling.`);
1053
+ console.log(`Configured as ${chainPosition} proxy in chain. Using relaxed timeouts for better stability.`);
1051
1054
  }
1052
1055
 
1053
- // Set hardcoded sensible defaults for all settings with chain-aware adjustments
1056
+ // Set sensible defaults with significantly relaxed timeouts
1054
1057
  this.settings = {
1055
1058
  ...settingsArg,
1056
1059
  targetIP: targetIP,
@@ -1060,39 +1063,39 @@ export class PortProxy {
1060
1063
  chainPosition: settingsArg.chainPosition || (isChainedProxy ? 'middle' : 'last'),
1061
1064
  aggressiveTlsRefresh: aggressiveTlsRefresh,
1062
1065
 
1063
- // Hardcoded timeout settings optimized for TLS safety in all deployment scenarios
1064
- initialDataTimeout: 60000, // 60 seconds for initial handshake
1065
- socketTimeout: socketTimeout, // Adjusted based on chain position
1066
- inactivityCheckInterval: isChainedProxy ? 30000 : 60000, // More frequent checks for chains
1067
- maxConnectionLifetime: isChainedProxy ? 2700000 : 3600000, // 45min or 1hr lifetime
1068
- inactivityTimeout: isChainedProxy ? 1200000 : 1800000, // 20min or 30min inactivity timeout
1066
+ // Much more relaxed timeout settings
1067
+ initialDataTimeout: 120000, // 2 minutes for initial handshake (doubled)
1068
+ socketTimeout: socketTimeout, // 5-6 hours based on chain position
1069
+ inactivityCheckInterval: 5 * 60 * 1000, // 5 minutes between checks (relaxed)
1070
+ maxConnectionLifetime: 12 * 60 * 60 * 1000, // 12 hours lifetime
1071
+ inactivityTimeout: 4 * 60 * 60 * 1000, // 4 hours inactivity timeout
1069
1072
 
1070
- gracefulShutdownTimeout: settingsArg.gracefulShutdownTimeout || 30000, // 30 seconds
1073
+ gracefulShutdownTimeout: settingsArg.gracefulShutdownTimeout || 60000, // 60 seconds
1071
1074
 
1072
1075
  // Socket optimization settings
1073
1076
  noDelay: settingsArg.noDelay !== undefined ? settingsArg.noDelay : true,
1074
1077
  keepAlive: settingsArg.keepAlive !== undefined ? settingsArg.keepAlive : true,
1075
- keepAliveInitialDelay: settingsArg.keepAliveInitialDelay || 10000, // 10 seconds
1076
- maxPendingDataSize: settingsArg.maxPendingDataSize || 10 * 1024 * 1024, // 10MB to handle large TLS handshakes
1078
+ keepAliveInitialDelay: settingsArg.keepAliveInitialDelay || 30000, // 30 seconds (increased)
1079
+ maxPendingDataSize: settingsArg.maxPendingDataSize || 20 * 1024 * 1024, // 20MB to handle large TLS handshakes
1077
1080
 
1078
1081
  // Feature flags - simplified with sensible defaults
1079
- disableInactivityCheck: false, // Always enable inactivity checks for TLS safety
1080
- enableKeepAliveProbes: true, // Always enable keep-alive probes for connection health
1082
+ disableInactivityCheck: false, // Still enable inactivity checks
1083
+ enableKeepAliveProbes: true, // Still enable keep-alive probes
1081
1084
  enableDetailedLogging: settingsArg.enableDetailedLogging || false,
1082
1085
  enableTlsDebugLogging: settingsArg.enableTlsDebugLogging || false,
1083
1086
  enableRandomizedTimeouts: settingsArg.enableRandomizedTimeouts || false,
1084
1087
 
1085
1088
  // Rate limiting defaults
1086
- maxConnectionsPerIP: settingsArg.maxConnectionsPerIP || 100, // 100 connections per IP
1087
- connectionRateLimitPerMinute: settingsArg.connectionRateLimitPerMinute || 300, // 300 per minute
1089
+ maxConnectionsPerIP: settingsArg.maxConnectionsPerIP || 200, // 200 connections per IP (doubled)
1090
+ connectionRateLimitPerMinute: settingsArg.connectionRateLimitPerMinute || 500, // 500 per minute (increased)
1088
1091
 
1089
- // Keep-alive settings with sensible defaults that ensure certificate safety
1090
- keepAliveTreatment: 'standard', // Always use standard treatment for certificate safety
1091
- keepAliveInactivityMultiplier: 2, // 2x normal inactivity timeout for minimal extension
1092
- // Use shorter lifetime for chained proxies
1092
+ // Keep-alive settings with much more relaxed defaults
1093
+ keepAliveTreatment: 'extended', // Use extended keep-alive treatment
1094
+ keepAliveInactivityMultiplier: 3, // 3x normal inactivity timeout for longer extension
1095
+ // Much longer keep-alive lifetimes
1093
1096
  extendedKeepAliveLifetime: isChainedProxy
1094
- ? 2 * 60 * 60 * 1000 // 2 hours for chained proxies
1095
- : 3 * 60 * 60 * 1000, // 3 hours for standalone proxies
1097
+ ? 24 * 60 * 60 * 1000 // 24 hours for chained proxies
1098
+ : 48 * 60 * 60 * 1000, // 48 hours for standalone proxies
1096
1099
  };
1097
1100
 
1098
1101
  // Store NetworkProxy instances if provided
@@ -1154,10 +1157,13 @@ export class PortProxy {
1154
1157
  );
1155
1158
  }
1156
1159
 
1157
- // Create a connection to the NetworkProxy
1160
+ // Create a connection to the NetworkProxy with optimized settings for reliability
1158
1161
  const proxySocket = plugins.net.connect({
1159
1162
  host: proxyHost,
1160
1163
  port: proxyPort,
1164
+ noDelay: true, // Disable Nagle's algorithm for NetworkProxy connections
1165
+ keepAlive: this.settings.keepAlive, // Use the same keepAlive setting as regular connections
1166
+ keepAliveInitialDelay: Math.max(this.settings.keepAliveInitialDelay - 5000, 5000) // Slightly faster
1161
1167
  });
1162
1168
 
1163
1169
  // Store the outgoing socket in the record
@@ -1165,6 +1171,30 @@ export class PortProxy {
1165
1171
  record.outgoingStartTime = Date.now();
1166
1172
  record.usingNetworkProxy = true;
1167
1173
  record.networkProxyIndex = proxyIndex;
1174
+
1175
+ // Mark keep-alive as enabled on outgoing if requested
1176
+ if (this.settings.keepAlive) {
1177
+ record.outgoingKeepAliveEnabled = true;
1178
+
1179
+ // Apply enhanced TCP keep-alive options if enabled
1180
+ if (this.settings.enableKeepAliveProbes) {
1181
+ try {
1182
+ if ('setKeepAliveProbes' in proxySocket) {
1183
+ (proxySocket as any).setKeepAliveProbes(10);
1184
+ }
1185
+ if ('setKeepAliveInterval' in proxySocket) {
1186
+ (proxySocket as any).setKeepAliveInterval(800);
1187
+ }
1188
+
1189
+ console.log(`[${connectionId}] Enhanced TCP keep-alive configured for NetworkProxy connection`);
1190
+ } catch (err) {
1191
+ // Ignore errors - these are optional enhancements
1192
+ if (this.settings.enableDetailedLogging) {
1193
+ console.log(`[${connectionId}] Enhanced keep-alive not supported for NetworkProxy: ${err}`);
1194
+ }
1195
+ }
1196
+ }
1197
+ }
1168
1198
 
1169
1199
  // Set up error handlers
1170
1200
  proxySocket.on('error', (err) => {
@@ -1213,6 +1243,41 @@ export class PortProxy {
1213
1243
 
1214
1244
  // Update activity on data transfer from the proxy socket
1215
1245
  proxySocket.on('data', () => this.updateActivity(record));
1246
+
1247
+ // Special handling for application-level keep-alives on NetworkProxy connections
1248
+ if (this.settings.keepAlive && record.isTLS) {
1249
+ // Set up a timer to periodically send application-level keep-alives
1250
+ const keepAliveTimer = setInterval(() => {
1251
+ if (proxySocket && !proxySocket.destroyed && record && !record.connectionClosed) {
1252
+ try {
1253
+ // Send 0-byte packet as application-level keep-alive
1254
+ proxySocket.write(Buffer.alloc(0));
1255
+
1256
+ if (this.settings.enableDetailedLogging) {
1257
+ console.log(`[${connectionId}] Sent application-level keep-alive to NetworkProxy connection`);
1258
+ }
1259
+ } catch (err) {
1260
+ // If we can't write, the connection is probably already dead
1261
+ if (this.settings.enableDetailedLogging) {
1262
+ console.log(`[${connectionId}] Error sending application-level keep-alive to NetworkProxy: ${err}`);
1263
+ }
1264
+
1265
+ // Stop the timer if we hit an error
1266
+ clearInterval(keepAliveTimer);
1267
+ }
1268
+ } else {
1269
+ // Clean up timer if connection is gone
1270
+ clearInterval(keepAliveTimer);
1271
+ }
1272
+ }, 60000); // Send keep-alive every minute
1273
+
1274
+ // Make sure interval doesn't prevent process exit
1275
+ if (keepAliveTimer.unref) {
1276
+ keepAliveTimer.unref();
1277
+ }
1278
+
1279
+ console.log(`[${connectionId}] Application-level keep-alive configured for NetworkProxy connection`);
1280
+ }
1216
1281
 
1217
1282
  if (this.settings.enableDetailedLogging) {
1218
1283
  console.log(
@@ -1334,17 +1399,25 @@ export class PortProxy {
1334
1399
 
1335
1400
  // Apply keep-alive settings to the outgoing connection as well
1336
1401
  if (this.settings.keepAlive) {
1337
- targetSocket.setKeepAlive(true, this.settings.keepAliveInitialDelay);
1402
+ // Use a slightly shorter initial delay for outgoing to ensure it stays active
1403
+ const outgoingInitialDelay = Math.max(this.settings.keepAliveInitialDelay - 5000, 5000);
1404
+ targetSocket.setKeepAlive(true, outgoingInitialDelay);
1405
+ record.outgoingKeepAliveEnabled = true;
1406
+
1407
+ console.log(`[${connectionId}] Keep-alive enabled on outgoing connection with initial delay: ${outgoingInitialDelay}ms`);
1338
1408
 
1339
1409
  // Apply enhanced TCP keep-alive options if enabled
1340
1410
  if (this.settings.enableKeepAliveProbes) {
1341
1411
  try {
1342
1412
  if ('setKeepAliveProbes' in targetSocket) {
1343
- (targetSocket as any).setKeepAliveProbes(10);
1413
+ (targetSocket as any).setKeepAliveProbes(10); // Same probes as incoming
1344
1414
  }
1345
1415
  if ('setKeepAliveInterval' in targetSocket) {
1346
- (targetSocket as any).setKeepAliveInterval(1000);
1416
+ // Use a shorter interval on outgoing for more reliable detection
1417
+ (targetSocket as any).setKeepAliveInterval(800); // Slightly faster than incoming
1347
1418
  }
1419
+
1420
+ console.log(`[${connectionId}] Enhanced TCP keep-alive probes configured on outgoing connection`);
1348
1421
  } catch (err) {
1349
1422
  // Ignore errors - these are optional enhancements
1350
1423
  if (this.settings.enableDetailedLogging) {
@@ -1354,6 +1427,43 @@ export class PortProxy {
1354
1427
  }
1355
1428
  }
1356
1429
  }
1430
+
1431
+ // Special handling for TLS keep-alive - we want to be more aggressive
1432
+ // with keeping the outgoing connection alive in TLS mode
1433
+ if (record.isTLS) {
1434
+ // Set a timer to periodically send empty data to keep connection alive
1435
+ // This is in addition to TCP keep-alive, works at application layer
1436
+ const keepAliveTimer = setInterval(() => {
1437
+ if (targetSocket && !targetSocket.destroyed && record && !record.connectionClosed) {
1438
+ try {
1439
+ // Send 0-byte packet as application-level keep-alive
1440
+ targetSocket.write(Buffer.alloc(0));
1441
+
1442
+ if (this.settings.enableDetailedLogging) {
1443
+ console.log(`[${connectionId}] Sent application-level keep-alive to outgoing TLS connection`);
1444
+ }
1445
+ } catch (err) {
1446
+ // If we can't write, the connection is probably already dead
1447
+ if (this.settings.enableDetailedLogging) {
1448
+ console.log(`[${connectionId}] Error sending application-level keep-alive: ${err}`);
1449
+ }
1450
+
1451
+ // Stop the timer if we hit an error
1452
+ clearInterval(keepAliveTimer);
1453
+ }
1454
+ } else {
1455
+ // Clean up timer if connection is gone
1456
+ clearInterval(keepAliveTimer);
1457
+ }
1458
+ }, 60000); // Send keep-alive every minute
1459
+
1460
+ // Make sure interval doesn't prevent process exit
1461
+ if (keepAliveTimer.unref) {
1462
+ keepAliveTimer.unref();
1463
+ }
1464
+
1465
+ console.log(`[${connectionId}] Application-level keep-alive configured for TLS outgoing connection`);
1466
+ }
1357
1467
  }
1358
1468
 
1359
1469
  // Setup specific error handler for connection phase with enhanced retries
@@ -1542,15 +1652,37 @@ export class PortProxy {
1542
1652
 
1543
1653
  // Set up the renegotiation listener *before* piping if this is a TLS connection with SNI
1544
1654
  if (serverName && record.isTLS) {
1545
- // This listener handles TLS renegotiation detection
1655
+ // Create a flag to prevent double-processing of the same handshake packet
1656
+ let processingRenegotiation = false;
1657
+
1658
+ // This listener handles TLS renegotiation detection on the incoming socket
1546
1659
  socket.on('data', (renegChunk) => {
1547
- if (renegChunk.length > 0 && renegChunk.readUInt8(0) === 22) {
1660
+ // Only check for content type 22 (handshake) and not already processing
1661
+ if (renegChunk.length > 0 && renegChunk.readUInt8(0) === 22 && !processingRenegotiation) {
1662
+ processingRenegotiation = true;
1663
+
1548
1664
  // Always update activity timestamp for any handshake packet
1549
1665
  this.updateActivity(record);
1550
1666
 
1551
1667
  try {
1668
+ // Enhanced logging for renegotiation
1669
+ console.log(`[${connectionId}] TLS handshake/renegotiation packet detected (${renegChunk.length} bytes)`);
1670
+
1552
1671
  // Extract all TLS information including session resumption data
1553
1672
  const sniInfo = extractSNIInfo(renegChunk, this.settings.enableTlsDebugLogging);
1673
+
1674
+ // Log details about the handshake packet
1675
+ if (this.settings.enableTlsDebugLogging) {
1676
+ console.log(`[${connectionId}] Handshake SNI extraction results:`, {
1677
+ isResumption: sniInfo?.isResumption || false,
1678
+ serverName: sniInfo?.serverName || 'none',
1679
+ resumedDomain: sniInfo?.resumedDomain || 'none',
1680
+ recordsExamined: sniInfo?.recordsExamined || 0,
1681
+ multipleRecords: sniInfo?.multipleRecords || false,
1682
+ partialExtract: sniInfo?.partialExtract || false
1683
+ });
1684
+ }
1685
+
1554
1686
  let newSNI = sniInfo?.serverName;
1555
1687
 
1556
1688
  // Handle session resumption - if we recognize the session ID, we know what domain it belongs to
@@ -1559,10 +1691,17 @@ export class PortProxy {
1559
1691
  newSNI = sniInfo.resumedDomain;
1560
1692
  }
1561
1693
 
1562
- // IMPORTANT: If we can't extract an SNI from renegotiation, we MUST allow it through
1694
+ // IMPORTANT: If we can't extract an SNI from renegotiation, but we detected a TLS handshake,
1695
+ // we still need to make sure it's properly forwarded to maintain the TLS state
1563
1696
  if (newSNI === undefined) {
1564
- console.log(`[${connectionId}] Rehandshake detected without SNI, allowing it through.`);
1565
- return;
1697
+ console.log(`[${connectionId}] Rehandshake detected without SNI, forwarding transparently.`);
1698
+
1699
+ // Set a temporary timeout to reset the processing flag
1700
+ setTimeout(() => {
1701
+ processingRenegotiation = false;
1702
+ }, 500);
1703
+
1704
+ return; // Let the piping handle the forwarding
1566
1705
  }
1567
1706
 
1568
1707
  // Check if the SNI has changed
@@ -1605,15 +1744,34 @@ export class PortProxy {
1605
1744
  } else {
1606
1745
  console.log(`[${connectionId}] Rehandshake SNI ${newSNI} not allowed. Terminating connection.`);
1607
1746
  this.initiateCleanupOnce(record, 'sni_mismatch');
1747
+ return;
1608
1748
  }
1609
1749
  } else {
1610
1750
  console.log(`[${connectionId}] Rehandshake with same SNI: ${newSNI}`);
1611
1751
  }
1612
1752
  } catch (err) {
1613
1753
  console.log(`[${connectionId}] Error processing renegotiation: ${err}. Allowing to continue.`);
1754
+ } finally {
1755
+ // Reset the processing flag after a small delay to prevent double-processing
1756
+ // of packets that may be part of the same handshake
1757
+ setTimeout(() => {
1758
+ processingRenegotiation = false;
1759
+ }, 500);
1614
1760
  }
1615
1761
  }
1616
1762
  });
1763
+
1764
+ // Set up a listener on the outgoing socket to detect issues with renegotiation
1765
+ // This helps catch cases where the outgoing connection has closed but the incoming is still active
1766
+ targetSocket.on('error', (err) => {
1767
+ // If we get an error during what might be a renegotiation, log it specially
1768
+ if (processingRenegotiation) {
1769
+ console.log(`[${connectionId}] ERROR: Outgoing socket error during TLS renegotiation: ${err.message}`);
1770
+ // Force immediate cleanup to prevent hanging connections
1771
+ this.initiateCleanupOnce(record, 'renegotiation_error');
1772
+ }
1773
+ // The normal error handler will be called for other errors
1774
+ });
1617
1775
  }
1618
1776
 
1619
1777
  // Now set up piping for future data and resume the socket
@@ -1651,15 +1809,37 @@ export class PortProxy {
1651
1809
  } else {
1652
1810
  // Set up the renegotiation listener *before* piping if this is a TLS connection with SNI
1653
1811
  if (serverName && record.isTLS) {
1654
- // This listener handles TLS renegotiation detection
1812
+ // Create a flag to prevent double-processing of the same handshake packet
1813
+ let processingRenegotiation = false;
1814
+
1815
+ // This listener handles TLS renegotiation detection on the incoming socket
1655
1816
  socket.on('data', (renegChunk) => {
1656
- if (renegChunk.length > 0 && renegChunk.readUInt8(0) === 22) {
1817
+ // Only check for content type 22 (handshake) and not already processing
1818
+ if (renegChunk.length > 0 && renegChunk.readUInt8(0) === 22 && !processingRenegotiation) {
1819
+ processingRenegotiation = true;
1820
+
1657
1821
  // Always update activity timestamp for any handshake packet
1658
1822
  this.updateActivity(record);
1659
1823
 
1660
1824
  try {
1825
+ // Enhanced logging for renegotiation
1826
+ console.log(`[${connectionId}] TLS handshake/renegotiation packet detected (${renegChunk.length} bytes)`);
1827
+
1661
1828
  // Extract all TLS information including session resumption data
1662
1829
  const sniInfo = extractSNIInfo(renegChunk, this.settings.enableTlsDebugLogging);
1830
+
1831
+ // Log details about the handshake packet
1832
+ if (this.settings.enableTlsDebugLogging) {
1833
+ console.log(`[${connectionId}] Handshake SNI extraction results:`, {
1834
+ isResumption: sniInfo?.isResumption || false,
1835
+ serverName: sniInfo?.serverName || 'none',
1836
+ resumedDomain: sniInfo?.resumedDomain || 'none',
1837
+ recordsExamined: sniInfo?.recordsExamined || 0,
1838
+ multipleRecords: sniInfo?.multipleRecords || false,
1839
+ partialExtract: sniInfo?.partialExtract || false
1840
+ });
1841
+ }
1842
+
1663
1843
  let newSNI = sniInfo?.serverName;
1664
1844
 
1665
1845
  // Handle session resumption - if we recognize the session ID, we know what domain it belongs to
@@ -1668,10 +1848,17 @@ export class PortProxy {
1668
1848
  newSNI = sniInfo.resumedDomain;
1669
1849
  }
1670
1850
 
1671
- // IMPORTANT: If we can't extract an SNI from renegotiation, we MUST allow it through
1851
+ // IMPORTANT: If we can't extract an SNI from renegotiation, but we detected a TLS handshake,
1852
+ // we still need to make sure it's properly forwarded to maintain the TLS state
1672
1853
  if (newSNI === undefined) {
1673
- console.log(`[${connectionId}] Rehandshake detected without SNI, allowing it through.`);
1674
- return;
1854
+ console.log(`[${connectionId}] Rehandshake detected without SNI, forwarding transparently.`);
1855
+
1856
+ // Set a temporary timeout to reset the processing flag
1857
+ setTimeout(() => {
1858
+ processingRenegotiation = false;
1859
+ }, 500);
1860
+
1861
+ return; // Let the piping handle the forwarding
1675
1862
  }
1676
1863
 
1677
1864
  // Check if the SNI has changed
@@ -1745,15 +1932,45 @@ export class PortProxy {
1745
1932
  } else {
1746
1933
  console.log(`[${connectionId}] Rehandshake SNI ${newSNI} not allowed. Terminating connection.`);
1747
1934
  this.initiateCleanupOnce(record, 'sni_mismatch');
1935
+ return;
1748
1936
  }
1749
1937
  } else {
1750
1938
  console.log(`[${connectionId}] Rehandshake with same SNI: ${newSNI}`);
1751
1939
  }
1752
1940
  } catch (err) {
1753
1941
  console.log(`[${connectionId}] Error processing renegotiation: ${err}. Allowing to continue.`);
1942
+ } finally {
1943
+ // Reset the processing flag after a small delay to prevent double-processing
1944
+ // of packets that may be part of the same handshake
1945
+ setTimeout(() => {
1946
+ processingRenegotiation = false;
1947
+ }, 500);
1754
1948
  }
1755
1949
  }
1756
1950
  });
1951
+
1952
+ // Set up a listener on the outgoing socket to detect issues with renegotiation
1953
+ // This helps catch cases where the outgoing connection has closed but the incoming is still active
1954
+ targetSocket.on('error', (err) => {
1955
+ // If we get an error during what might be a renegotiation, log it specially
1956
+ if (processingRenegotiation) {
1957
+ console.log(`[${connectionId}] ERROR: Outgoing socket error during TLS renegotiation: ${err.message}`);
1958
+ // Force immediate cleanup to prevent hanging connections
1959
+ this.initiateCleanupOnce(record, 'renegotiation_error');
1960
+ }
1961
+ // The normal error handler will be called for other errors
1962
+ });
1963
+
1964
+ // Also monitor targetSocket for connection issues during client handshakes
1965
+ targetSocket.on('close', () => {
1966
+ // If the outgoing socket closes during renegotiation, it's a critical issue
1967
+ if (processingRenegotiation) {
1968
+ console.log(`[${connectionId}] CRITICAL: Outgoing socket closed during TLS renegotiation!`);
1969
+ console.log(`[${connectionId}] This likely explains cert mismatch errors in the browser.`);
1970
+ // Force immediate cleanup on the client side
1971
+ this.initiateCleanupOnce(record, 'target_closed_during_renegotiation');
1972
+ }
1973
+ });
1757
1974
  }
1758
1975
 
1759
1976
  // Now set up piping
@@ -1974,15 +2191,16 @@ export class PortProxy {
1974
2191
  if (record.lastActivity > 0) {
1975
2192
  const timeDiff = now - record.lastActivity;
1976
2193
 
1977
- // Enhanced sleep detection with graduated thresholds
1978
- // For chained proxies, we need to be more aggressive about refreshing connections
1979
- const isChainedProxy = this.settings.targetIP === 'localhost' || this.settings.targetIP === '127.0.0.1';
2194
+ // Enhanced sleep detection with graduated thresholds - much more relaxed
2195
+ // Using chain detection from settings instead of recalculating
2196
+ const isChainedProxy = this.settings.isChainedProxy || false;
1980
2197
  const minuteInMs = 60 * 1000;
2198
+ const hourInMs = 60 * minuteInMs;
1981
2199
 
1982
- // Different thresholds based on connection type and configuration
1983
- const shortInactivityThreshold = isChainedProxy ? 10 * minuteInMs : 15 * minuteInMs;
1984
- const mediumInactivityThreshold = isChainedProxy ? 20 * minuteInMs : 30 * minuteInMs;
1985
- const longInactivityThreshold = isChainedProxy ? 60 * minuteInMs : 120 * minuteInMs;
2200
+ // Significantly relaxed thresholds for better stability
2201
+ const shortInactivityThreshold = 30 * minuteInMs; // 30 minutes
2202
+ const mediumInactivityThreshold = 2 * hourInMs; // 2 hours
2203
+ const longInactivityThreshold = 8 * hourInMs; // 8 hours
1986
2204
 
1987
2205
  // Short inactivity (10-15 mins) - Might be temporary network issue or short sleep
1988
2206
  if (timeDiff > shortInactivityThreshold) {
@@ -2467,12 +2685,20 @@ export class PortProxy {
2467
2685
 
2468
2686
  // Initialize sleep detection fields
2469
2687
  possibleSystemSleep: false,
2688
+
2689
+ // Track keep-alive state for both sides of the connection
2690
+ incomingKeepAliveEnabled: false,
2691
+ outgoingKeepAliveEnabled: false,
2470
2692
  };
2471
2693
 
2472
2694
  // Apply keep-alive settings if enabled
2473
2695
  if (this.settings.keepAlive) {
2696
+ // Configure incoming socket keep-alive
2474
2697
  socket.setKeepAlive(true, this.settings.keepAliveInitialDelay);
2475
2698
  connectionRecord.hasKeepAlive = true; // Mark connection as having keep-alive
2699
+ connectionRecord.incomingKeepAliveEnabled = true;
2700
+
2701
+ console.log(`[${connectionId}] Keep-alive enabled on incoming connection with initial delay: ${this.settings.keepAliveInitialDelay}ms`);
2476
2702
 
2477
2703
  // Apply enhanced TCP keep-alive options if enabled
2478
2704
  if (this.settings.enableKeepAliveProbes) {
@@ -2484,6 +2710,8 @@ export class PortProxy {
2484
2710
  if ('setKeepAliveInterval' in socket) {
2485
2711
  (socket as any).setKeepAliveInterval(1000); // 1 second interval between probes
2486
2712
  }
2713
+
2714
+ console.log(`[${connectionId}] Enhanced TCP keep-alive probes configured on incoming connection`);
2487
2715
  } catch (err) {
2488
2716
  // Ignore errors - these are optional enhancements
2489
2717
  if (this.settings.enableDetailedLogging) {