@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/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/classes.portproxy.js +254 -56
- package/package.json +1 -1
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.portproxy.ts +283 -55
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@push.rocks/smartproxy",
|
|
3
|
-
"version": "3.32.
|
|
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",
|
package/ts/00_commitinfo_data.ts
CHANGED
|
@@ -3,6 +3,6 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export const commitinfo = {
|
|
5
5
|
name: '@push.rocks/smartproxy',
|
|
6
|
-
version: '3.32.
|
|
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
|
}
|
package/ts/classes.portproxy.ts
CHANGED
|
@@ -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:
|
|
141
|
-
expiryTime: 24 * 60 * 60 * 1000, // 24 hours
|
|
142
|
-
cleanupInterval:
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
|
1038
|
-
socketTimeout =
|
|
1040
|
+
// First proxy handling browser connections
|
|
1041
|
+
socketTimeout = 6 * 60 * 60 * 1000; // 6 hours
|
|
1039
1042
|
break;
|
|
1040
1043
|
case 'middle':
|
|
1041
|
-
// Middle proxies
|
|
1042
|
-
socketTimeout =
|
|
1044
|
+
// Middle proxies
|
|
1045
|
+
socketTimeout = 5 * 60 * 60 * 1000; // 5 hours
|
|
1043
1046
|
break;
|
|
1044
1047
|
case 'last':
|
|
1045
|
-
// Last proxy
|
|
1046
|
-
socketTimeout =
|
|
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
|
|
1053
|
+
console.log(`Configured as ${chainPosition} proxy in chain. Using relaxed timeouts for better stability.`);
|
|
1051
1054
|
}
|
|
1052
1055
|
|
|
1053
|
-
// Set
|
|
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
|
-
//
|
|
1064
|
-
initialDataTimeout:
|
|
1065
|
-
socketTimeout: socketTimeout, //
|
|
1066
|
-
inactivityCheckInterval:
|
|
1067
|
-
maxConnectionLifetime:
|
|
1068
|
-
inactivityTimeout:
|
|
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 ||
|
|
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 ||
|
|
1076
|
-
maxPendingDataSize: settingsArg.maxPendingDataSize ||
|
|
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, //
|
|
1080
|
-
enableKeepAliveProbes: true, //
|
|
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 ||
|
|
1087
|
-
connectionRateLimitPerMinute: settingsArg.connectionRateLimitPerMinute ||
|
|
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
|
|
1090
|
-
keepAliveTreatment: '
|
|
1091
|
-
keepAliveInactivityMultiplier:
|
|
1092
|
-
//
|
|
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
|
-
?
|
|
1095
|
-
:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
|
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,
|
|
1565
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
|
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,
|
|
1674
|
-
|
|
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
|
-
//
|
|
1979
|
-
const isChainedProxy = this.settings.
|
|
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
|
-
//
|
|
1983
|
-
const shortInactivityThreshold =
|
|
1984
|
-
const mediumInactivityThreshold =
|
|
1985
|
-
const longInactivityThreshold =
|
|
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) {
|