@push.rocks/smartproxy 19.5.20 → 19.5.22
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/index.d.ts +1 -0
- package/dist_ts/core/utils/index.js +2 -1
- package/dist_ts/core/utils/proxy-protocol.d.ts +45 -0
- package/dist_ts/core/utils/proxy-protocol.js +201 -0
- package/dist_ts/proxies/smart-proxy/connection-manager.js +3 -1
- package/dist_ts/proxies/smart-proxy/models/interfaces.d.ts +1 -0
- package/dist_ts/proxies/smart-proxy/models/route-types.d.ts +1 -0
- package/dist_ts/proxies/smart-proxy/models/route-types.js +1 -1
- package/dist_ts/proxies/smart-proxy/route-connection-handler.js +125 -24
- package/package.json +1 -1
- package/readme.hints.md +8 -1
- package/readme.plan.md +23 -19
- package/readme.proxy-chain-summary.md +112 -0
- package/readme.proxy-protocol-example.md +462 -0
- package/readme.proxy-protocol.md +415 -0
- package/ts/core/utils/index.ts +1 -0
- package/ts/core/utils/proxy-protocol.ts +246 -0
- package/ts/proxies/smart-proxy/connection-manager.ts +2 -0
- package/ts/proxies/smart-proxy/models/interfaces.ts +1 -0
- package/ts/proxies/smart-proxy/models/route-types.ts +3 -0
- package/ts/proxies/smart-proxy/route-connection-handler.ts +134 -27
|
@@ -13,6 +13,7 @@ import { SharedRouteManager as RouteManager } from '../../core/routing/route-man
|
|
|
13
13
|
import { cleanupSocket, createIndependentSocketHandlers, setupSocketHandlers, createSocketWithErrorHandler, setupBidirectionalForwarding } from '../../core/utils/socket-utils.js';
|
|
14
14
|
import { WrappedSocket } from '../../core/models/wrapped-socket.js';
|
|
15
15
|
import { getUnderlyingSocket } from '../../core/models/socket-types.js';
|
|
16
|
+
import { ProxyProtocolParser } from '../../core/utils/proxy-protocol.js';
|
|
16
17
|
|
|
17
18
|
/**
|
|
18
19
|
* Handles new connection processing and setup logic with support for route-based configuration
|
|
@@ -198,25 +199,29 @@ export class RouteConnectionHandler {
|
|
|
198
199
|
setupSocketHandlers(
|
|
199
200
|
underlyingSocket,
|
|
200
201
|
(reason) => {
|
|
201
|
-
//
|
|
202
|
-
//
|
|
203
|
-
|
|
204
|
-
|
|
202
|
+
// Always cleanup when incoming socket closes
|
|
203
|
+
// This prevents connection accumulation in proxy chains
|
|
204
|
+
logger.log('debug', `Connection ${connectionId} closed during immediate routing: ${reason}`, {
|
|
205
|
+
connectionId,
|
|
206
|
+
remoteIP: record.remoteIP,
|
|
207
|
+
reason,
|
|
208
|
+
hasOutgoing: !!record.outgoing,
|
|
209
|
+
outgoingState: record.outgoing?.readyState,
|
|
210
|
+
component: 'route-handler'
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
// If there's a pending or established outgoing connection, destroy it
|
|
214
|
+
if (record.outgoing && !record.outgoing.destroyed) {
|
|
215
|
+
logger.log('debug', `Destroying outgoing connection for ${connectionId}`, {
|
|
205
216
|
connectionId,
|
|
206
|
-
|
|
207
|
-
reason,
|
|
208
|
-
hasOutgoing: !!record.outgoing,
|
|
209
|
-
outgoingState: record.outgoing?.readyState,
|
|
217
|
+
outgoingState: record.outgoing.readyState,
|
|
210
218
|
component: 'route-handler'
|
|
211
219
|
});
|
|
212
|
-
|
|
213
|
-
// If there's a pending outgoing connection, destroy it
|
|
214
|
-
if (record.outgoing && !record.outgoing.destroyed) {
|
|
215
|
-
record.outgoing.destroy();
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
this.connectionManager.cleanupConnection(record, reason);
|
|
220
|
+
record.outgoing.destroy();
|
|
219
221
|
}
|
|
222
|
+
|
|
223
|
+
// Always cleanup the connection record
|
|
224
|
+
this.connectionManager.cleanupConnection(record, reason);
|
|
220
225
|
},
|
|
221
226
|
undefined, // Use default timeout handler
|
|
222
227
|
'immediate-route-client'
|
|
@@ -295,17 +300,8 @@ export class RouteConnectionHandler {
|
|
|
295
300
|
}
|
|
296
301
|
});
|
|
297
302
|
|
|
298
|
-
//
|
|
299
|
-
|
|
300
|
-
// Clear the initial timeout since we've received data
|
|
301
|
-
if (initialTimeout) {
|
|
302
|
-
clearTimeout(initialTimeout);
|
|
303
|
-
initialTimeout = null;
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
initialDataReceived = true;
|
|
307
|
-
record.hasReceivedInitialData = true;
|
|
308
|
-
|
|
303
|
+
// Handler for processing initial data (after potential PROXY protocol)
|
|
304
|
+
const processInitialData = (chunk: Buffer) => {
|
|
309
305
|
// Block non-TLS connections on port 443
|
|
310
306
|
if (!this.tlsManager.isTlsHandshake(chunk) && localPort === 443) {
|
|
311
307
|
logger.log('warn', `Non-TLS connection ${connectionId} detected on port 443. Terminating connection - only TLS traffic is allowed on standard HTTPS port.`, {
|
|
@@ -381,6 +377,67 @@ export class RouteConnectionHandler {
|
|
|
381
377
|
|
|
382
378
|
// Find the appropriate route for this connection
|
|
383
379
|
this.routeConnection(socket, record, serverName, chunk);
|
|
380
|
+
};
|
|
381
|
+
|
|
382
|
+
// First data handler to capture initial TLS handshake or PROXY protocol
|
|
383
|
+
socket.once('data', async (chunk: Buffer) => {
|
|
384
|
+
// Clear the initial timeout since we've received data
|
|
385
|
+
if (initialTimeout) {
|
|
386
|
+
clearTimeout(initialTimeout);
|
|
387
|
+
initialTimeout = null;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
initialDataReceived = true;
|
|
391
|
+
record.hasReceivedInitialData = true;
|
|
392
|
+
|
|
393
|
+
// Check if this is from a trusted proxy and might have PROXY protocol
|
|
394
|
+
if (this.settings.proxyIPs?.includes(socket.remoteAddress || '') && this.settings.acceptProxyProtocol !== false) {
|
|
395
|
+
// Check if this starts with PROXY protocol
|
|
396
|
+
if (chunk.toString('ascii', 0, Math.min(6, chunk.length)).startsWith('PROXY ')) {
|
|
397
|
+
try {
|
|
398
|
+
const parseResult = ProxyProtocolParser.parse(chunk);
|
|
399
|
+
|
|
400
|
+
if (parseResult.proxyInfo) {
|
|
401
|
+
// Update the wrapped socket with real client info (if it's a WrappedSocket)
|
|
402
|
+
if (socket instanceof WrappedSocket) {
|
|
403
|
+
socket.setProxyInfo(parseResult.proxyInfo.sourceIP, parseResult.proxyInfo.sourcePort);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// Update connection record with real client info
|
|
407
|
+
record.remoteIP = parseResult.proxyInfo.sourceIP;
|
|
408
|
+
record.remotePort = parseResult.proxyInfo.sourcePort;
|
|
409
|
+
|
|
410
|
+
logger.log('info', `PROXY protocol parsed successfully`, {
|
|
411
|
+
connectionId,
|
|
412
|
+
realClientIP: parseResult.proxyInfo.sourceIP,
|
|
413
|
+
realClientPort: parseResult.proxyInfo.sourcePort,
|
|
414
|
+
proxyIP: socket.remoteAddress,
|
|
415
|
+
component: 'route-handler'
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
// Process remaining data if any
|
|
419
|
+
if (parseResult.remainingData.length > 0) {
|
|
420
|
+
processInitialData(parseResult.remainingData);
|
|
421
|
+
} else {
|
|
422
|
+
// Wait for more data
|
|
423
|
+
socket.once('data', processInitialData);
|
|
424
|
+
}
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
} catch (error) {
|
|
428
|
+
logger.log('error', `Failed to parse PROXY protocol from trusted proxy`, {
|
|
429
|
+
connectionId,
|
|
430
|
+
error: error.message,
|
|
431
|
+
proxyIP: socket.remoteAddress,
|
|
432
|
+
component: 'route-handler'
|
|
433
|
+
});
|
|
434
|
+
// Continue processing as normal data
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// Process as normal data (no PROXY protocol)
|
|
440
|
+
processInitialData(chunk);
|
|
384
441
|
});
|
|
385
442
|
}
|
|
386
443
|
|
|
@@ -1119,7 +1176,7 @@ export class RouteConnectionHandler {
|
|
|
1119
1176
|
// Clean up the connection record - this is critical!
|
|
1120
1177
|
this.connectionManager.cleanupConnection(record, `connection_failed_${(error as any).code || 'unknown'}`);
|
|
1121
1178
|
},
|
|
1122
|
-
onConnect: () => {
|
|
1179
|
+
onConnect: async () => {
|
|
1123
1180
|
if (this.settings.enableDetailedLogging) {
|
|
1124
1181
|
logger.log('info', `Connection ${connectionId} established to target ${finalTargetHost}:${finalTargetPort}`, {
|
|
1125
1182
|
connectionId,
|
|
@@ -1135,6 +1192,56 @@ export class RouteConnectionHandler {
|
|
|
1135
1192
|
// Add the normal error handler for established connections
|
|
1136
1193
|
targetSocket.on('error', this.connectionManager.handleError('outgoing', record));
|
|
1137
1194
|
|
|
1195
|
+
// Check if we should send PROXY protocol header
|
|
1196
|
+
const shouldSendProxyProtocol = record.routeConfig?.action?.sendProxyProtocol ||
|
|
1197
|
+
this.settings.sendProxyProtocol;
|
|
1198
|
+
|
|
1199
|
+
if (shouldSendProxyProtocol) {
|
|
1200
|
+
try {
|
|
1201
|
+
// Generate PROXY protocol header
|
|
1202
|
+
const proxyInfo = {
|
|
1203
|
+
protocol: (record.remoteIP.includes(':') ? 'TCP6' : 'TCP4') as 'TCP4' | 'TCP6',
|
|
1204
|
+
sourceIP: record.remoteIP,
|
|
1205
|
+
sourcePort: record.remotePort || socket.remotePort || 0,
|
|
1206
|
+
destinationIP: socket.localAddress || '',
|
|
1207
|
+
destinationPort: socket.localPort || 0
|
|
1208
|
+
};
|
|
1209
|
+
|
|
1210
|
+
const proxyHeader = ProxyProtocolParser.generate(proxyInfo);
|
|
1211
|
+
|
|
1212
|
+
// Send PROXY protocol header first
|
|
1213
|
+
await new Promise<void>((resolve, reject) => {
|
|
1214
|
+
targetSocket.write(proxyHeader, (err) => {
|
|
1215
|
+
if (err) {
|
|
1216
|
+
logger.log('error', `Failed to send PROXY protocol header`, {
|
|
1217
|
+
connectionId,
|
|
1218
|
+
error: err.message,
|
|
1219
|
+
component: 'route-handler'
|
|
1220
|
+
});
|
|
1221
|
+
reject(err);
|
|
1222
|
+
} else {
|
|
1223
|
+
logger.log('info', `PROXY protocol header sent to backend`, {
|
|
1224
|
+
connectionId,
|
|
1225
|
+
targetHost: finalTargetHost,
|
|
1226
|
+
targetPort: finalTargetPort,
|
|
1227
|
+
sourceIP: proxyInfo.sourceIP,
|
|
1228
|
+
sourcePort: proxyInfo.sourcePort,
|
|
1229
|
+
component: 'route-handler'
|
|
1230
|
+
});
|
|
1231
|
+
resolve();
|
|
1232
|
+
}
|
|
1233
|
+
});
|
|
1234
|
+
});
|
|
1235
|
+
} catch (error) {
|
|
1236
|
+
logger.log('error', `Error sending PROXY protocol header`, {
|
|
1237
|
+
connectionId,
|
|
1238
|
+
error: error.message,
|
|
1239
|
+
component: 'route-handler'
|
|
1240
|
+
});
|
|
1241
|
+
// Continue anyway - don't break the connection
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1138
1245
|
// Flush any pending data to target
|
|
1139
1246
|
if (record.pendingData.length > 0) {
|
|
1140
1247
|
const combinedData = Buffer.concat(record.pendingData);
|