@push.rocks/smartproxy 19.5.16 → 19.5.18
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/core/utils/socket-utils.d.ts +19 -1
- package/dist_ts/core/utils/socket-utils.js +64 -6
- package/dist_ts/proxies/smart-proxy/http-proxy-bridge.js +21 -26
- package/dist_ts/proxies/smart-proxy/route-connection-handler.d.ts +0 -5
- package/dist_ts/proxies/smart-proxy/route-connection-handler.js +85 -132
- package/package.json +1 -1
- package/readme.hints.md +118 -1
- package/ts/core/utils/socket-utils.ts +89 -5
- package/ts/proxies/smart-proxy/http-proxy-bridge.ts +20 -30
- package/ts/proxies/smart-proxy/route-connection-handler.ts +93 -170
|
@@ -109,7 +109,8 @@ export function createSocketCleanupHandler(
|
|
|
109
109
|
export function createIndependentSocketHandlers(
|
|
110
110
|
clientSocket: plugins.net.Socket | plugins.tls.TLSSocket,
|
|
111
111
|
serverSocket: plugins.net.Socket | plugins.tls.TLSSocket,
|
|
112
|
-
onBothClosed: (reason: string) => void
|
|
112
|
+
onBothClosed: (reason: string) => void,
|
|
113
|
+
options: { enableHalfOpen?: boolean } = {}
|
|
113
114
|
): { cleanupClient: (reason: string) => Promise<void>, cleanupServer: (reason: string) => Promise<void> } {
|
|
114
115
|
let clientClosed = false;
|
|
115
116
|
let serverClosed = false;
|
|
@@ -127,8 +128,13 @@ export function createIndependentSocketHandlers(
|
|
|
127
128
|
clientClosed = true;
|
|
128
129
|
clientReason = reason;
|
|
129
130
|
|
|
130
|
-
//
|
|
131
|
-
if (!serverClosed &&
|
|
131
|
+
// Default behavior: close both sockets when one closes (required for proxy chains)
|
|
132
|
+
if (!serverClosed && !options.enableHalfOpen) {
|
|
133
|
+
serverSocket.destroy();
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Half-open support (opt-in only)
|
|
137
|
+
if (!serverClosed && serverSocket.writable && options.enableHalfOpen) {
|
|
132
138
|
// Half-close: stop reading from client, let server finish
|
|
133
139
|
clientSocket.pause();
|
|
134
140
|
clientSocket.unpipe(serverSocket);
|
|
@@ -145,8 +151,13 @@ export function createIndependentSocketHandlers(
|
|
|
145
151
|
serverClosed = true;
|
|
146
152
|
serverReason = reason;
|
|
147
153
|
|
|
148
|
-
//
|
|
149
|
-
if (!clientClosed &&
|
|
154
|
+
// Default behavior: close both sockets when one closes (required for proxy chains)
|
|
155
|
+
if (!clientClosed && !options.enableHalfOpen) {
|
|
156
|
+
clientSocket.destroy();
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Half-open support (opt-in only)
|
|
160
|
+
if (!clientClosed && clientSocket.writable && options.enableHalfOpen) {
|
|
150
161
|
// Half-close: stop reading from server, let client finish
|
|
151
162
|
serverSocket.pause();
|
|
152
163
|
serverSocket.unpipe(clientSocket);
|
|
@@ -194,6 +205,79 @@ export function setupSocketHandlers(
|
|
|
194
205
|
});
|
|
195
206
|
}
|
|
196
207
|
|
|
208
|
+
/**
|
|
209
|
+
* Setup bidirectional data forwarding between two sockets with proper cleanup
|
|
210
|
+
* @param clientSocket The client/incoming socket
|
|
211
|
+
* @param serverSocket The server/outgoing socket
|
|
212
|
+
* @param handlers Object containing optional handlers for data and cleanup
|
|
213
|
+
* @returns Cleanup functions for both sockets
|
|
214
|
+
*/
|
|
215
|
+
export function setupBidirectionalForwarding(
|
|
216
|
+
clientSocket: plugins.net.Socket | plugins.tls.TLSSocket,
|
|
217
|
+
serverSocket: plugins.net.Socket | plugins.tls.TLSSocket,
|
|
218
|
+
handlers: {
|
|
219
|
+
onClientData?: (chunk: Buffer) => void;
|
|
220
|
+
onServerData?: (chunk: Buffer) => void;
|
|
221
|
+
onCleanup: (reason: string) => void;
|
|
222
|
+
enableHalfOpen?: boolean;
|
|
223
|
+
}
|
|
224
|
+
): { cleanupClient: (reason: string) => Promise<void>, cleanupServer: (reason: string) => Promise<void> } {
|
|
225
|
+
// Set up cleanup handlers
|
|
226
|
+
const { cleanupClient, cleanupServer } = createIndependentSocketHandlers(
|
|
227
|
+
clientSocket,
|
|
228
|
+
serverSocket,
|
|
229
|
+
handlers.onCleanup,
|
|
230
|
+
{ enableHalfOpen: handlers.enableHalfOpen }
|
|
231
|
+
);
|
|
232
|
+
|
|
233
|
+
// Set up error and close handlers
|
|
234
|
+
setupSocketHandlers(clientSocket, cleanupClient, undefined, 'client');
|
|
235
|
+
setupSocketHandlers(serverSocket, cleanupServer, undefined, 'server');
|
|
236
|
+
|
|
237
|
+
// Set up data forwarding with backpressure handling
|
|
238
|
+
clientSocket.on('data', (chunk: Buffer) => {
|
|
239
|
+
if (handlers.onClientData) {
|
|
240
|
+
handlers.onClientData(chunk);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (serverSocket.writable) {
|
|
244
|
+
const flushed = serverSocket.write(chunk);
|
|
245
|
+
|
|
246
|
+
// Handle backpressure
|
|
247
|
+
if (!flushed) {
|
|
248
|
+
clientSocket.pause();
|
|
249
|
+
serverSocket.once('drain', () => {
|
|
250
|
+
if (!clientSocket.destroyed) {
|
|
251
|
+
clientSocket.resume();
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
serverSocket.on('data', (chunk: Buffer) => {
|
|
259
|
+
if (handlers.onServerData) {
|
|
260
|
+
handlers.onServerData(chunk);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (clientSocket.writable) {
|
|
264
|
+
const flushed = clientSocket.write(chunk);
|
|
265
|
+
|
|
266
|
+
// Handle backpressure
|
|
267
|
+
if (!flushed) {
|
|
268
|
+
serverSocket.pause();
|
|
269
|
+
clientSocket.once('drain', () => {
|
|
270
|
+
if (!serverSocket.destroyed) {
|
|
271
|
+
serverSocket.resume();
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
return { cleanupClient, cleanupServer };
|
|
279
|
+
}
|
|
280
|
+
|
|
197
281
|
/**
|
|
198
282
|
* Pipe two sockets together with proper cleanup on either end
|
|
199
283
|
* @param socket1 First socket
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as plugins from '../../plugins.js';
|
|
2
2
|
import { HttpProxy } from '../http-proxy/index.js';
|
|
3
|
+
import { setupBidirectionalForwarding } from '../../core/utils/socket-utils.js';
|
|
3
4
|
import type { IConnectionRecord, ISmartProxyOptions } from './models/interfaces.js';
|
|
4
5
|
import type { IRouteConfig } from './models/route-types.js';
|
|
5
6
|
|
|
@@ -123,36 +124,25 @@ export class HttpProxyBridge {
|
|
|
123
124
|
proxySocket.write(initialChunk);
|
|
124
125
|
}
|
|
125
126
|
|
|
126
|
-
//
|
|
127
|
-
socket
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
if (!proxySocket.destroyed) {
|
|
146
|
-
proxySocket.destroy();
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
cleanupCallback(reason);
|
|
150
|
-
};
|
|
151
|
-
|
|
152
|
-
socket.on('end', () => cleanup('socket_end'));
|
|
153
|
-
socket.on('error', () => cleanup('socket_error'));
|
|
154
|
-
proxySocket.on('end', () => cleanup('proxy_end'));
|
|
155
|
-
proxySocket.on('error', () => cleanup('proxy_error'));
|
|
127
|
+
// Use centralized bidirectional forwarding
|
|
128
|
+
setupBidirectionalForwarding(socket, proxySocket, {
|
|
129
|
+
onClientData: (chunk) => {
|
|
130
|
+
// Update stats if needed
|
|
131
|
+
if (record) {
|
|
132
|
+
record.bytesReceived += chunk.length;
|
|
133
|
+
}
|
|
134
|
+
},
|
|
135
|
+
onServerData: (chunk) => {
|
|
136
|
+
// Update stats if needed
|
|
137
|
+
if (record) {
|
|
138
|
+
record.bytesSent += chunk.length;
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
onCleanup: (reason) => {
|
|
142
|
+
cleanupCallback(reason);
|
|
143
|
+
},
|
|
144
|
+
enableHalfOpen: false // Close both when one closes (required for proxy chains)
|
|
145
|
+
});
|
|
156
146
|
}
|
|
157
147
|
|
|
158
148
|
/**
|
|
@@ -9,7 +9,7 @@ import { TlsManager } from './tls-manager.js';
|
|
|
9
9
|
import { HttpProxyBridge } from './http-proxy-bridge.js';
|
|
10
10
|
import { TimeoutManager } from './timeout-manager.js';
|
|
11
11
|
import { RouteManager } from './route-manager.js';
|
|
12
|
-
import { cleanupSocket, createIndependentSocketHandlers, setupSocketHandlers, createSocketWithErrorHandler } from '../../core/utils/socket-utils.js';
|
|
12
|
+
import { cleanupSocket, createIndependentSocketHandlers, setupSocketHandlers, createSocketWithErrorHandler, setupBidirectionalForwarding } from '../../core/utils/socket-utils.js';
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* Handles new connection processing and setup logic with support for route-based configuration
|
|
@@ -176,8 +176,33 @@ export class RouteConnectionHandler {
|
|
|
176
176
|
|
|
177
177
|
// If no routes require TLS handling and it's not port 443, route immediately
|
|
178
178
|
if (!needsTlsHandling && localPort !== 443) {
|
|
179
|
-
// Set up
|
|
180
|
-
|
|
179
|
+
// Set up proper socket handlers for immediate routing
|
|
180
|
+
setupSocketHandlers(
|
|
181
|
+
socket,
|
|
182
|
+
(reason) => {
|
|
183
|
+
// Only cleanup if connection hasn't been fully established
|
|
184
|
+
// Check if outgoing connection exists and is connected
|
|
185
|
+
if (!record.outgoing || record.outgoing.readyState !== 'open') {
|
|
186
|
+
logger.log('debug', `Connection ${connectionId} closed during immediate routing: ${reason}`, {
|
|
187
|
+
connectionId,
|
|
188
|
+
remoteIP: record.remoteIP,
|
|
189
|
+
reason,
|
|
190
|
+
hasOutgoing: !!record.outgoing,
|
|
191
|
+
outgoingState: record.outgoing?.readyState,
|
|
192
|
+
component: 'route-handler'
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
// If there's a pending outgoing connection, destroy it
|
|
196
|
+
if (record.outgoing && !record.outgoing.destroyed) {
|
|
197
|
+
record.outgoing.destroy();
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
this.connectionManager.cleanupConnection(record, reason);
|
|
201
|
+
}
|
|
202
|
+
},
|
|
203
|
+
undefined, // Use default timeout handler
|
|
204
|
+
'immediate-route-client'
|
|
205
|
+
);
|
|
181
206
|
|
|
182
207
|
// Route immediately for non-TLS connections
|
|
183
208
|
this.routeConnection(socket, record, '', undefined);
|
|
@@ -221,6 +246,37 @@ export class RouteConnectionHandler {
|
|
|
221
246
|
// Set up error handler
|
|
222
247
|
socket.on('error', this.connectionManager.handleError('incoming', record));
|
|
223
248
|
|
|
249
|
+
// Add close/end handlers to catch immediate disconnections
|
|
250
|
+
socket.once('close', () => {
|
|
251
|
+
if (!initialDataReceived) {
|
|
252
|
+
logger.log('warn', `Connection ${connectionId} closed before sending initial data`, {
|
|
253
|
+
connectionId,
|
|
254
|
+
remoteIP: record.remoteIP,
|
|
255
|
+
component: 'route-handler'
|
|
256
|
+
});
|
|
257
|
+
if (initialTimeout) {
|
|
258
|
+
clearTimeout(initialTimeout);
|
|
259
|
+
initialTimeout = null;
|
|
260
|
+
}
|
|
261
|
+
this.connectionManager.cleanupConnection(record, 'closed_before_data');
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
socket.once('end', () => {
|
|
266
|
+
if (!initialDataReceived) {
|
|
267
|
+
logger.log('debug', `Connection ${connectionId} ended before sending initial data`, {
|
|
268
|
+
connectionId,
|
|
269
|
+
remoteIP: record.remoteIP,
|
|
270
|
+
component: 'route-handler'
|
|
271
|
+
});
|
|
272
|
+
if (initialTimeout) {
|
|
273
|
+
clearTimeout(initialTimeout);
|
|
274
|
+
initialTimeout = null;
|
|
275
|
+
}
|
|
276
|
+
// Don't cleanup on 'end' - wait for 'close'
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
|
|
224
280
|
// First data handler to capture initial TLS handshake
|
|
225
281
|
socket.once('data', (chunk: Buffer) => {
|
|
226
282
|
// Clear the initial timeout since we've received data
|
|
@@ -927,107 +983,6 @@ export class RouteConnectionHandler {
|
|
|
927
983
|
}
|
|
928
984
|
}
|
|
929
985
|
|
|
930
|
-
/**
|
|
931
|
-
* Setup improved error handling for the outgoing connection
|
|
932
|
-
* @deprecated This method is no longer used - error handling is done in createSocketWithErrorHandler
|
|
933
|
-
*/
|
|
934
|
-
private setupOutgoingErrorHandler(
|
|
935
|
-
connectionId: string,
|
|
936
|
-
targetSocket: plugins.net.Socket,
|
|
937
|
-
record: IConnectionRecord,
|
|
938
|
-
socket: plugins.net.Socket,
|
|
939
|
-
finalTargetHost: string,
|
|
940
|
-
finalTargetPort: number
|
|
941
|
-
): void {
|
|
942
|
-
targetSocket.once('error', (err) => {
|
|
943
|
-
// This handler runs only once during the initial connection phase
|
|
944
|
-
const code = (err as any).code;
|
|
945
|
-
logger.log('error',
|
|
946
|
-
`Connection setup error for ${connectionId} to ${finalTargetHost}:${finalTargetPort}: ${err.message} (${code})`,
|
|
947
|
-
{
|
|
948
|
-
connectionId,
|
|
949
|
-
targetHost: finalTargetHost,
|
|
950
|
-
targetPort: finalTargetPort,
|
|
951
|
-
errorMessage: err.message,
|
|
952
|
-
errorCode: code,
|
|
953
|
-
component: 'route-handler'
|
|
954
|
-
}
|
|
955
|
-
);
|
|
956
|
-
|
|
957
|
-
// Resume the incoming socket to prevent it from hanging
|
|
958
|
-
socket.resume();
|
|
959
|
-
|
|
960
|
-
// Log specific error types for easier debugging
|
|
961
|
-
if (code === 'ECONNREFUSED') {
|
|
962
|
-
logger.log('error',
|
|
963
|
-
`Connection ${connectionId}: Target ${finalTargetHost}:${finalTargetPort} refused connection. Check if the target service is running and listening on that port.`,
|
|
964
|
-
{
|
|
965
|
-
connectionId,
|
|
966
|
-
targetHost: finalTargetHost,
|
|
967
|
-
targetPort: finalTargetPort,
|
|
968
|
-
recommendation: 'Check if the target service is running and listening on that port.',
|
|
969
|
-
component: 'route-handler'
|
|
970
|
-
}
|
|
971
|
-
);
|
|
972
|
-
} else if (code === 'ETIMEDOUT') {
|
|
973
|
-
logger.log('error',
|
|
974
|
-
`Connection ${connectionId} to ${finalTargetHost}:${finalTargetPort} timed out. Check network conditions, firewall rules, or if the target is too far away.`,
|
|
975
|
-
{
|
|
976
|
-
connectionId,
|
|
977
|
-
targetHost: finalTargetHost,
|
|
978
|
-
targetPort: finalTargetPort,
|
|
979
|
-
recommendation: 'Check network conditions, firewall rules, or if the target is too far away.',
|
|
980
|
-
component: 'route-handler'
|
|
981
|
-
}
|
|
982
|
-
);
|
|
983
|
-
} else if (code === 'ECONNRESET') {
|
|
984
|
-
logger.log('error',
|
|
985
|
-
`Connection ${connectionId} to ${finalTargetHost}:${finalTargetPort} was reset. The target might have closed the connection abruptly.`,
|
|
986
|
-
{
|
|
987
|
-
connectionId,
|
|
988
|
-
targetHost: finalTargetHost,
|
|
989
|
-
targetPort: finalTargetPort,
|
|
990
|
-
recommendation: 'The target might have closed the connection abruptly.',
|
|
991
|
-
component: 'route-handler'
|
|
992
|
-
}
|
|
993
|
-
);
|
|
994
|
-
} else if (code === 'EHOSTUNREACH') {
|
|
995
|
-
logger.log('error',
|
|
996
|
-
`Connection ${connectionId}: Host ${finalTargetHost} is unreachable. Check DNS settings, network routing, or firewall rules.`,
|
|
997
|
-
{
|
|
998
|
-
connectionId,
|
|
999
|
-
targetHost: finalTargetHost,
|
|
1000
|
-
recommendation: 'Check DNS settings, network routing, or firewall rules.',
|
|
1001
|
-
component: 'route-handler'
|
|
1002
|
-
}
|
|
1003
|
-
);
|
|
1004
|
-
} else if (code === 'ENOTFOUND') {
|
|
1005
|
-
logger.log('error',
|
|
1006
|
-
`Connection ${connectionId}: DNS lookup failed for ${finalTargetHost}. Check your DNS settings or if the hostname is correct.`,
|
|
1007
|
-
{
|
|
1008
|
-
connectionId,
|
|
1009
|
-
targetHost: finalTargetHost,
|
|
1010
|
-
recommendation: 'Check your DNS settings or if the hostname is correct.',
|
|
1011
|
-
component: 'route-handler'
|
|
1012
|
-
}
|
|
1013
|
-
);
|
|
1014
|
-
}
|
|
1015
|
-
|
|
1016
|
-
// Clear any existing error handler after connection phase
|
|
1017
|
-
targetSocket.removeAllListeners('error');
|
|
1018
|
-
|
|
1019
|
-
// Re-add the normal error handler for established connections
|
|
1020
|
-
targetSocket.on('error', this.connectionManager.handleError('outgoing', record));
|
|
1021
|
-
|
|
1022
|
-
if (record.outgoingTerminationReason === null) {
|
|
1023
|
-
record.outgoingTerminationReason = 'connection_failed';
|
|
1024
|
-
this.connectionManager.incrementTerminationStat('outgoing', 'connection_failed');
|
|
1025
|
-
}
|
|
1026
|
-
|
|
1027
|
-
// Clean up the connection
|
|
1028
|
-
this.connectionManager.initiateCleanupOnce(record, `connection_failed_${code}`);
|
|
1029
|
-
});
|
|
1030
|
-
}
|
|
1031
986
|
|
|
1032
987
|
/**
|
|
1033
988
|
* Sets up a direct connection to the target
|
|
@@ -1090,6 +1045,16 @@ export class RouteConnectionHandler {
|
|
|
1090
1045
|
host: finalTargetHost,
|
|
1091
1046
|
onError: (error) => {
|
|
1092
1047
|
// Connection failed - clean up everything immediately
|
|
1048
|
+
// Check if connection record is still valid (client might have disconnected)
|
|
1049
|
+
if (record.connectionClosed) {
|
|
1050
|
+
logger.log('debug', `Backend connection failed but client already disconnected for ${connectionId}`, {
|
|
1051
|
+
connectionId,
|
|
1052
|
+
errorCode: (error as any).code,
|
|
1053
|
+
component: 'route-handler'
|
|
1054
|
+
});
|
|
1055
|
+
return;
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1093
1058
|
logger.log('error',
|
|
1094
1059
|
`Connection setup error for ${connectionId} to ${finalTargetHost}:${finalTargetPort}: ${error.message} (${(error as any).code})`,
|
|
1095
1060
|
{
|
|
@@ -1117,10 +1082,12 @@ export class RouteConnectionHandler {
|
|
|
1117
1082
|
}
|
|
1118
1083
|
|
|
1119
1084
|
// Resume the incoming socket to prevent it from hanging
|
|
1120
|
-
socket.
|
|
1085
|
+
if (socket && !socket.destroyed) {
|
|
1086
|
+
socket.resume();
|
|
1087
|
+
}
|
|
1121
1088
|
|
|
1122
1089
|
// Clean up the incoming socket
|
|
1123
|
-
if (!socket.destroyed) {
|
|
1090
|
+
if (socket && !socket.destroyed) {
|
|
1124
1091
|
socket.destroy();
|
|
1125
1092
|
}
|
|
1126
1093
|
|
|
@@ -1170,65 +1137,27 @@ export class RouteConnectionHandler {
|
|
|
1170
1137
|
record.pendingDataSize = 0;
|
|
1171
1138
|
}
|
|
1172
1139
|
|
|
1173
|
-
//
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1140
|
+
// Use centralized bidirectional forwarding setup
|
|
1141
|
+
setupBidirectionalForwarding(socket, targetSocket, {
|
|
1142
|
+
onClientData: (chunk) => {
|
|
1143
|
+
record.bytesReceived += chunk.length;
|
|
1144
|
+
this.timeoutManager.updateActivity(record);
|
|
1145
|
+
},
|
|
1146
|
+
onServerData: (chunk) => {
|
|
1147
|
+
record.bytesSent += chunk.length;
|
|
1148
|
+
this.timeoutManager.updateActivity(record);
|
|
1149
|
+
},
|
|
1150
|
+
onCleanup: (reason) => {
|
|
1178
1151
|
this.connectionManager.cleanupConnection(record, reason);
|
|
1179
|
-
}
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
// Setup socket handlers with custom timeout handling
|
|
1183
|
-
setupSocketHandlers(socket, cleanupClient, (sock) => {
|
|
1184
|
-
// Don't close on timeout for keep-alive connections
|
|
1185
|
-
if (record.hasKeepAlive) {
|
|
1186
|
-
sock.setTimeout(this.settings.socketTimeout || 3600000);
|
|
1187
|
-
}
|
|
1188
|
-
}, 'client');
|
|
1189
|
-
|
|
1190
|
-
setupSocketHandlers(targetSocket, cleanupServer, (sock) => {
|
|
1191
|
-
// Don't close on timeout for keep-alive connections
|
|
1192
|
-
if (record.hasKeepAlive) {
|
|
1193
|
-
sock.setTimeout(this.settings.socketTimeout || 3600000);
|
|
1194
|
-
}
|
|
1195
|
-
}, 'server');
|
|
1196
|
-
|
|
1197
|
-
// Forward data from client to target with backpressure handling
|
|
1198
|
-
socket.on('data', (chunk: Buffer) => {
|
|
1199
|
-
record.bytesReceived += chunk.length;
|
|
1200
|
-
this.timeoutManager.updateActivity(record);
|
|
1201
|
-
|
|
1202
|
-
if (targetSocket.writable) {
|
|
1203
|
-
const flushed = targetSocket.write(chunk);
|
|
1204
|
-
|
|
1205
|
-
// Handle backpressure
|
|
1206
|
-
if (!flushed) {
|
|
1207
|
-
socket.pause();
|
|
1208
|
-
targetSocket.once('drain', () => {
|
|
1209
|
-
socket.resume();
|
|
1210
|
-
});
|
|
1211
|
-
}
|
|
1212
|
-
}
|
|
1213
|
-
});
|
|
1214
|
-
|
|
1215
|
-
// Forward data from target to client with backpressure handling
|
|
1216
|
-
targetSocket.on('data', (chunk: Buffer) => {
|
|
1217
|
-
record.bytesSent += chunk.length;
|
|
1218
|
-
this.timeoutManager.updateActivity(record);
|
|
1219
|
-
|
|
1220
|
-
if (socket.writable) {
|
|
1221
|
-
const flushed = socket.write(chunk);
|
|
1222
|
-
|
|
1223
|
-
// Handle backpressure
|
|
1224
|
-
if (!flushed) {
|
|
1225
|
-
targetSocket.pause();
|
|
1226
|
-
socket.once('drain', () => {
|
|
1227
|
-
targetSocket.resume();
|
|
1228
|
-
});
|
|
1229
|
-
}
|
|
1230
|
-
}
|
|
1152
|
+
},
|
|
1153
|
+
enableHalfOpen: false // Default: close both when one closes (required for proxy chains)
|
|
1231
1154
|
});
|
|
1155
|
+
|
|
1156
|
+
// Apply timeouts if keep-alive is enabled
|
|
1157
|
+
if (record.hasKeepAlive) {
|
|
1158
|
+
socket.setTimeout(this.settings.socketTimeout || 3600000);
|
|
1159
|
+
targetSocket.setTimeout(this.settings.socketTimeout || 3600000);
|
|
1160
|
+
}
|
|
1232
1161
|
|
|
1233
1162
|
// Log successful connection
|
|
1234
1163
|
logger.log('info',
|
|
@@ -1294,7 +1223,7 @@ export class RouteConnectionHandler {
|
|
|
1294
1223
|
}
|
|
1295
1224
|
});
|
|
1296
1225
|
|
|
1297
|
-
//
|
|
1226
|
+
// Set outgoing socket immediately so it can be cleaned up if client disconnects
|
|
1298
1227
|
record.outgoing = targetSocket;
|
|
1299
1228
|
record.outgoingStartTime = Date.now();
|
|
1300
1229
|
|
|
@@ -1387,11 +1316,5 @@ export class RouteConnectionHandler {
|
|
|
1387
1316
|
|
|
1388
1317
|
// Apply socket timeouts
|
|
1389
1318
|
this.timeoutManager.applySocketTimeouts(record);
|
|
1390
|
-
|
|
1391
|
-
// Track outgoing data for bytes counting (moved from the duplicate connect handler)
|
|
1392
|
-
targetSocket.on('data', (chunk: Buffer) => {
|
|
1393
|
-
record.bytesSent += chunk.length;
|
|
1394
|
-
this.timeoutManager.updateActivity(record);
|
|
1395
|
-
});
|
|
1396
1319
|
}
|
|
1397
1320
|
}
|