@push.rocks/smartproxy 19.5.2 → 19.5.3

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.
@@ -146,18 +146,42 @@ export class RouteConnectionHandler {
146
146
  );
147
147
  }
148
148
 
149
- // Start TLS SNI handling
150
- this.handleTlsConnection(socket, record);
149
+ // Handle the connection - wait for initial data to determine if it's TLS
150
+ this.handleInitialData(socket, record);
151
151
  }
152
152
 
153
153
  /**
154
- * Handle a connection and wait for TLS handshake for SNI extraction if needed
154
+ * Handle initial data from a connection to determine routing
155
155
  */
156
- private handleTlsConnection(socket: plugins.net.Socket, record: IConnectionRecord): void {
156
+ private handleInitialData(socket: plugins.net.Socket, record: IConnectionRecord): void {
157
157
  const connectionId = record.id;
158
158
  const localPort = record.localPort;
159
159
  let initialDataReceived = false;
160
160
 
161
+ // Check if any routes on this port require TLS handling
162
+ const allRoutes = this.routeManager.getAllRoutes();
163
+ const needsTlsHandling = allRoutes.some(route => {
164
+ // Check if route matches this port
165
+ const matchesPort = this.routeManager.getRoutesForPort(localPort).includes(route);
166
+
167
+ return matchesPort &&
168
+ route.action.type === 'forward' &&
169
+ route.action.tls &&
170
+ (route.action.tls.mode === 'terminate' ||
171
+ route.action.tls.mode === 'passthrough');
172
+ });
173
+
174
+ // If no routes require TLS handling and it's not port 443, route immediately
175
+ if (!needsTlsHandling && localPort !== 443) {
176
+ // Set up error handler
177
+ socket.on('error', this.connectionManager.handleError('incoming', record));
178
+
179
+ // Route immediately for non-TLS connections
180
+ this.routeConnection(socket, record, '', undefined);
181
+ return;
182
+ }
183
+
184
+ // Otherwise, wait for initial data to check if it's TLS
161
185
  // Set an initial timeout for handshake data
162
186
  let initialTimeout: NodeJS.Timeout | null = setTimeout(() => {
163
187
  if (!initialDataReceived) {
@@ -296,6 +320,12 @@ export class RouteConnectionHandler {
296
320
  const localPort = record.localPort;
297
321
  const remoteIP = record.remoteIP;
298
322
 
323
+ // Check if this is an HTTP proxy port
324
+ const isHttpProxyPort = this.settings.useHttpProxy?.includes(localPort);
325
+
326
+ // For HTTP proxy ports without TLS, skip domain check since domain info comes from HTTP headers
327
+ const skipDomainCheck = isHttpProxyPort && !record.isTLS;
328
+
299
329
  // Find matching route
300
330
  const routeMatch = this.routeManager.findMatchingRoute({
301
331
  port: localPort,
@@ -303,6 +333,7 @@ export class RouteConnectionHandler {
303
333
  clientIp: remoteIP,
304
334
  path: undefined, // We don't have path info at this point
305
335
  tlsVersion: undefined, // We don't extract TLS version yet
336
+ skipDomainCheck: skipDomainCheck,
306
337
  });
307
338
 
308
339
  if (!routeMatch) {
@@ -382,6 +413,56 @@ export class RouteConnectionHandler {
382
413
  });
383
414
  }
384
415
 
416
+ // Apply route-specific security checks
417
+ if (route.security) {
418
+ // Check IP allow/block lists
419
+ if (route.security.ipAllowList || route.security.ipBlockList) {
420
+ const isIPAllowed = this.securityManager.isIPAuthorized(
421
+ remoteIP,
422
+ route.security.ipAllowList || [],
423
+ route.security.ipBlockList || []
424
+ );
425
+
426
+ if (!isIPAllowed) {
427
+ logger.log('warn', `IP ${remoteIP} blocked by route security for route ${route.name || 'unnamed'} (connection: ${connectionId})`, {
428
+ connectionId,
429
+ remoteIP,
430
+ routeName: route.name || 'unnamed',
431
+ component: 'route-handler'
432
+ });
433
+ socket.end();
434
+ this.connectionManager.cleanupConnection(record, 'route_ip_blocked');
435
+ return;
436
+ }
437
+ }
438
+
439
+ // Check max connections per route
440
+ if (route.security.maxConnections !== undefined) {
441
+ // TODO: Implement per-route connection tracking
442
+ // For now, log that this feature is not yet implemented
443
+ if (this.settings.enableDetailedLogging) {
444
+ logger.log('warn', `Route ${route.name} has maxConnections=${route.security.maxConnections} configured but per-route connection limits are not yet implemented`, {
445
+ connectionId,
446
+ routeName: route.name,
447
+ component: 'route-handler'
448
+ });
449
+ }
450
+ }
451
+
452
+ // Check authentication requirements
453
+ if (route.security.authentication || route.security.basicAuth || route.security.jwtAuth) {
454
+ // Authentication checks would typically happen at the HTTP layer
455
+ // For non-HTTP connections or passthrough, we can't enforce authentication
456
+ if (route.action.type === 'forward' && route.action.tls?.mode !== 'terminate') {
457
+ logger.log('warn', `Route ${route.name} has authentication configured but it cannot be enforced for non-terminated connections`, {
458
+ connectionId,
459
+ routeName: route.name,
460
+ tlsMode: route.action.tls?.mode || 'none',
461
+ component: 'route-handler'
462
+ });
463
+ }
464
+ }
465
+ }
385
466
 
386
467
  // Handle the route based on its action type
387
468
  switch (route.action.type) {
@@ -634,6 +715,18 @@ export class RouteConnectionHandler {
634
715
  // No TLS settings - check if this port should use HttpProxy
635
716
  const isHttpProxyPort = this.settings.useHttpProxy?.includes(record.localPort);
636
717
 
718
+ // Debug logging
719
+ if (this.settings.enableDetailedLogging) {
720
+ logger.log('debug', `Checking HttpProxy forwarding: port=${record.localPort}, useHttpProxy=${JSON.stringify(this.settings.useHttpProxy)}, isHttpProxyPort=${isHttpProxyPort}, hasHttpProxy=${!!this.httpProxyBridge.getHttpProxy()}`, {
721
+ connectionId,
722
+ localPort: record.localPort,
723
+ useHttpProxy: this.settings.useHttpProxy,
724
+ isHttpProxyPort,
725
+ hasHttpProxy: !!this.httpProxyBridge.getHttpProxy(),
726
+ component: 'route-handler'
727
+ });
728
+ }
729
+
637
730
  if (isHttpProxyPort && this.httpProxyBridge.getHttpProxy()) {
638
731
  // Forward non-TLS connections to HttpProxy if configured
639
732
  if (this.settings.enableDetailedLogging) {
@@ -211,9 +211,10 @@ export class RouteManager extends plugins.EventEmitter {
211
211
 
212
212
  /**
213
213
  * Check if a client IP is allowed by a route's security settings
214
+ * @deprecated Security is now checked in route-connection-handler.ts after route matching
214
215
  */
215
216
  private isClientIpAllowed(route: IRouteConfig, clientIp: string): boolean {
216
- const security = route.action.security;
217
+ const security = route.security;
217
218
 
218
219
  if (!security) {
219
220
  return true; // No security settings means allowed
@@ -330,8 +331,9 @@ export class RouteManager extends plugins.EventEmitter {
330
331
  clientIp: string;
331
332
  path?: string;
332
333
  tlsVersion?: string;
334
+ skipDomainCheck?: boolean;
333
335
  }): IRouteMatchResult | null {
334
- const { port, domain, clientIp, path, tlsVersion } = options;
336
+ const { port, domain, clientIp, path, tlsVersion, skipDomainCheck } = options;
335
337
 
336
338
  // Get all routes for this port
337
339
  const routesForPort = this.getRoutesForPort(port);
@@ -340,7 +342,7 @@ export class RouteManager extends plugins.EventEmitter {
340
342
  for (const route of routesForPort) {
341
343
  // Check domain match
342
344
  // If the route has domain restrictions and we have a domain to check
343
- if (route.match.domains) {
345
+ if (route.match.domains && !skipDomainCheck) {
344
346
  // If no domain was provided (non-TLS or no SNI), this route doesn't match
345
347
  if (!domain) {
346
348
  continue;
@@ -351,6 +353,7 @@ export class RouteManager extends plugins.EventEmitter {
351
353
  }
352
354
  }
353
355
  // If route has no domain restrictions, it matches all domains
356
+ // If skipDomainCheck is true, we skip domain validation for HTTP connections
354
357
 
355
358
  // Check path match if specified in both route and request
356
359
  if (path && route.match.path) {
@@ -371,12 +374,8 @@ export class RouteManager extends plugins.EventEmitter {
371
374
  continue;
372
375
  }
373
376
 
374
- // Check security settings
375
- if (!this.isClientIpAllowed(route, clientIp)) {
376
- continue;
377
- }
378
-
379
377
  // All checks passed, this route matches
378
+ // NOTE: Security is checked AFTER route matching in route-connection-handler.ts
380
379
  return { route };
381
380
  }
382
381
 
@@ -625,14 +625,6 @@ export function createNfTablesRoute(
625
625
  }
626
626
  };
627
627
 
628
- // Add security if allowed or blocked IPs are specified
629
- if (options.ipAllowList?.length || options.ipBlockList?.length) {
630
- action.security = {
631
- ipAllowList: options.ipAllowList,
632
- ipBlockList: options.ipBlockList
633
- };
634
- }
635
-
636
628
  // Add TLS options if needed
637
629
  if (options.useTls) {
638
630
  action.tls = {
@@ -641,11 +633,21 @@ export function createNfTablesRoute(
641
633
  }
642
634
 
643
635
  // Create the route config
644
- return {
636
+ const routeConfig: IRouteConfig = {
645
637
  name,
646
638
  match,
647
639
  action
648
640
  };
641
+
642
+ // Add security if allowed or blocked IPs are specified
643
+ if (options.ipAllowList?.length || options.ipBlockList?.length) {
644
+ routeConfig.security = {
645
+ ipAllowList: options.ipAllowList,
646
+ ipBlockList: options.ipBlockList
647
+ };
648
+ }
649
+
650
+ return routeConfig;
649
651
  }
650
652
 
651
653
  /**