@push.rocks/smartproxy 19.5.20 → 19.5.21

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
@@ -295,17 +296,8 @@ export class RouteConnectionHandler {
295
296
  }
296
297
  });
297
298
 
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
-
299
+ // Handler for processing initial data (after potential PROXY protocol)
300
+ const processInitialData = (chunk: Buffer) => {
309
301
  // Block non-TLS connections on port 443
310
302
  if (!this.tlsManager.isTlsHandshake(chunk) && localPort === 443) {
311
303
  logger.log('warn', `Non-TLS connection ${connectionId} detected on port 443. Terminating connection - only TLS traffic is allowed on standard HTTPS port.`, {
@@ -381,6 +373,67 @@ export class RouteConnectionHandler {
381
373
 
382
374
  // Find the appropriate route for this connection
383
375
  this.routeConnection(socket, record, serverName, chunk);
376
+ };
377
+
378
+ // First data handler to capture initial TLS handshake or PROXY protocol
379
+ socket.once('data', async (chunk: Buffer) => {
380
+ // Clear the initial timeout since we've received data
381
+ if (initialTimeout) {
382
+ clearTimeout(initialTimeout);
383
+ initialTimeout = null;
384
+ }
385
+
386
+ initialDataReceived = true;
387
+ record.hasReceivedInitialData = true;
388
+
389
+ // Check if this is from a trusted proxy and might have PROXY protocol
390
+ if (this.settings.proxyIPs?.includes(socket.remoteAddress || '') && this.settings.acceptProxyProtocol !== false) {
391
+ // Check if this starts with PROXY protocol
392
+ if (chunk.toString('ascii', 0, Math.min(6, chunk.length)).startsWith('PROXY ')) {
393
+ try {
394
+ const parseResult = ProxyProtocolParser.parse(chunk);
395
+
396
+ if (parseResult.proxyInfo) {
397
+ // Update the wrapped socket with real client info (if it's a WrappedSocket)
398
+ if (socket instanceof WrappedSocket) {
399
+ socket.setProxyInfo(parseResult.proxyInfo.sourceIP, parseResult.proxyInfo.sourcePort);
400
+ }
401
+
402
+ // Update connection record with real client info
403
+ record.remoteIP = parseResult.proxyInfo.sourceIP;
404
+ record.remotePort = parseResult.proxyInfo.sourcePort;
405
+
406
+ logger.log('info', `PROXY protocol parsed successfully`, {
407
+ connectionId,
408
+ realClientIP: parseResult.proxyInfo.sourceIP,
409
+ realClientPort: parseResult.proxyInfo.sourcePort,
410
+ proxyIP: socket.remoteAddress,
411
+ component: 'route-handler'
412
+ });
413
+
414
+ // Process remaining data if any
415
+ if (parseResult.remainingData.length > 0) {
416
+ processInitialData(parseResult.remainingData);
417
+ } else {
418
+ // Wait for more data
419
+ socket.once('data', processInitialData);
420
+ }
421
+ return;
422
+ }
423
+ } catch (error) {
424
+ logger.log('error', `Failed to parse PROXY protocol from trusted proxy`, {
425
+ connectionId,
426
+ error: error.message,
427
+ proxyIP: socket.remoteAddress,
428
+ component: 'route-handler'
429
+ });
430
+ // Continue processing as normal data
431
+ }
432
+ }
433
+ }
434
+
435
+ // Process as normal data (no PROXY protocol)
436
+ processInitialData(chunk);
384
437
  });
385
438
  }
386
439
 
@@ -1119,7 +1172,7 @@ export class RouteConnectionHandler {
1119
1172
  // Clean up the connection record - this is critical!
1120
1173
  this.connectionManager.cleanupConnection(record, `connection_failed_${(error as any).code || 'unknown'}`);
1121
1174
  },
1122
- onConnect: () => {
1175
+ onConnect: async () => {
1123
1176
  if (this.settings.enableDetailedLogging) {
1124
1177
  logger.log('info', `Connection ${connectionId} established to target ${finalTargetHost}:${finalTargetPort}`, {
1125
1178
  connectionId,
@@ -1135,6 +1188,56 @@ export class RouteConnectionHandler {
1135
1188
  // Add the normal error handler for established connections
1136
1189
  targetSocket.on('error', this.connectionManager.handleError('outgoing', record));
1137
1190
 
1191
+ // Check if we should send PROXY protocol header
1192
+ const shouldSendProxyProtocol = record.routeConfig?.action?.sendProxyProtocol ||
1193
+ this.settings.sendProxyProtocol;
1194
+
1195
+ if (shouldSendProxyProtocol) {
1196
+ try {
1197
+ // Generate PROXY protocol header
1198
+ const proxyInfo = {
1199
+ protocol: (record.remoteIP.includes(':') ? 'TCP6' : 'TCP4') as 'TCP4' | 'TCP6',
1200
+ sourceIP: record.remoteIP,
1201
+ sourcePort: record.remotePort || socket.remotePort || 0,
1202
+ destinationIP: socket.localAddress || '',
1203
+ destinationPort: socket.localPort || 0
1204
+ };
1205
+
1206
+ const proxyHeader = ProxyProtocolParser.generate(proxyInfo);
1207
+
1208
+ // Send PROXY protocol header first
1209
+ await new Promise<void>((resolve, reject) => {
1210
+ targetSocket.write(proxyHeader, (err) => {
1211
+ if (err) {
1212
+ logger.log('error', `Failed to send PROXY protocol header`, {
1213
+ connectionId,
1214
+ error: err.message,
1215
+ component: 'route-handler'
1216
+ });
1217
+ reject(err);
1218
+ } else {
1219
+ logger.log('info', `PROXY protocol header sent to backend`, {
1220
+ connectionId,
1221
+ targetHost: finalTargetHost,
1222
+ targetPort: finalTargetPort,
1223
+ sourceIP: proxyInfo.sourceIP,
1224
+ sourcePort: proxyInfo.sourcePort,
1225
+ component: 'route-handler'
1226
+ });
1227
+ resolve();
1228
+ }
1229
+ });
1230
+ });
1231
+ } catch (error) {
1232
+ logger.log('error', `Error sending PROXY protocol header`, {
1233
+ connectionId,
1234
+ error: error.message,
1235
+ component: 'route-handler'
1236
+ });
1237
+ // Continue anyway - don't break the connection
1238
+ }
1239
+ }
1240
+
1138
1241
  // Flush any pending data to target
1139
1242
  if (record.pendingData.length > 0) {
1140
1243
  const combinedData = Buffer.concat(record.pendingData);