@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.
@@ -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
- // Only cleanup if connection hasn't been fully established
202
- // Check if outgoing connection exists and is connected
203
- if (!record.outgoing || record.outgoing.readyState !== 'open') {
204
- logger.log('debug', `Connection ${connectionId} closed during immediate routing: ${reason}`, {
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
- remoteIP: record.remoteIP,
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
- // First data handler to capture initial TLS handshake
299
- socket.once('data', (chunk: Buffer) => {
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);