smartproxy/docs/porthandling.md

10 KiB

SmartProxy Port Handling

This document covers all the port handling capabilities in SmartProxy, including port range specification, dynamic port mapping, and runtime port management.

Port Range Syntax

SmartProxy offers flexible port range specification through the TPortRange type, which can be defined in three different ways:

1. Single Port

// Match a single port
{
  match: {
    ports: 443
  }
}

2. Array of Specific Ports

// Match multiple specific ports
{
  match: {
    ports: [80, 443, 8080]
  }
}

3. Port Range

// Match a range of ports
{
  match: {
    ports: [{ from: 8000, to: 8100 }]
  }
}

4. Mixed Port Specifications

You can combine different port specification methods in a single rule:

// Match both specific ports and port ranges
{
  match: {
    ports: [80, 443, { from: 8000, to: 8100 }]
  }
}

Port Forwarding Options

SmartProxy offers several ways to handle port forwarding from source to target:

1. Static Port Forwarding

Forward to a fixed target port:

{
  action: {
    type: 'forward',
    target: {
      host: 'backend.example.com',
      port: 8080
    }
  }
}

2. Preserve Source Port

Forward to the same port on the target:

{
  action: {
    type: 'forward',
    target: {
      host: 'backend.example.com',
      port: 'preserve'
    }
  }
}

3. Dynamic Port Mapping

Use a function to determine the target port based on connection context:

{
  action: {
    type: 'forward',
    target: {
      host: 'backend.example.com',
      port: (context) => {
        // Calculate port based on request details
        return 8000 + (context.port % 100);
      }
    }
  }
}

Port Selection Context

When using dynamic port mapping functions, you have access to a rich context object that provides details about the connection:

interface IRouteContext {
  // Connection information
  port: number;          // The matched incoming port
  domain?: string;       // The domain from SNI or Host header
  clientIp: string;      // The client's IP address
  serverIp: string;      // The server's IP address
  path?: string;         // URL path (for HTTP connections)
  query?: string;        // Query string (for HTTP connections)
  headers?: Record<string, string>; // HTTP headers (for HTTP connections)

  // TLS information
  isTls: boolean;        // Whether the connection is TLS
  tlsVersion?: string;   // TLS version if applicable

  // Route information
  routeName?: string;    // The name of the matched route
  routeId?: string;      // The ID of the matched route

  // Additional properties
  timestamp: number;     // The request timestamp
  connectionId: string;  // Unique connection identifier
}

Common Port Mapping Patterns

1. Port Offset Mapping

Forward traffic to target ports with a fixed offset:

{
  action: {
    type: 'forward',
    target: {
      host: 'backend.example.com',
      port: (context) => context.port + 1000
    }
  }
}

2. Domain-Based Port Mapping

Forward to different backend ports based on the domain:

{
  action: {
    type: 'forward',
    target: {
      host: 'backend.example.com',
      port: (context) => {
        switch (context.domain) {
          case 'api.example.com': return 8001;
          case 'admin.example.com': return 8002;
          case 'staging.example.com': return 8003;
          default: return 8000;
        }
      }
    }
  }
}

3. Load Balancing with Hash-Based Distribution

Distribute connections across a port range using a deterministic hash function:

{
  action: {
    type: 'forward',
    target: {
      host: 'backend.example.com',
      port: (context) => {
        // Simple hash function to ensure consistent mapping
        const hostname = context.domain || '';
        const hash = hostname.split('').reduce((a, b) => a + b.charCodeAt(0), 0);
        return 8000 + (hash % 10); // Map to ports 8000-8009
      }
    }
  }
}

IPv6-Mapped IPv4 Compatibility

SmartProxy automatically handles IPv6-mapped IPv4 addresses for optimal compatibility. When a connection from an IPv4 address (e.g., 192.168.1.1) arrives as an IPv6-mapped address (::ffff:192.168.1.1), the system normalizes these addresses for consistent matching.

This is particularly important when:

  1. Matching client IP restrictions in route configurations
  2. Preserving source IP for outgoing connections
  3. Tracking connections and rate limits

No special configuration is needed - the system handles this normalization automatically.

Dynamic Port Management

SmartProxy allows for runtime port configuration changes without requiring a restart.

Adding and Removing Ports

// Get the SmartProxy instance
const proxy = new SmartProxy({ /* config */ });

// Add a new listening port
await proxy.addListeningPort(8081);

// Remove a listening port
await proxy.removeListeningPort(8082);

Runtime Route Updates

// Get current routes
const currentRoutes = proxy.getRoutes();

// Add new route for the new port
const newRoute = {
  name: 'New Dynamic Route',
  match: {
    ports: 8081,
    domains: ['dynamic.example.com']
  },
  action: {
    type: 'forward',
    target: {
      host: 'backend.example.com',
      port: 9000
    }
  }
};

// Update the route configuration
await proxy.updateRoutes([...currentRoutes, newRoute]);

// Remove routes for a specific port
const routesWithout8082 = currentRoutes.filter(route => {
  const ports = proxy.routeManager.expandPortRange(route.match.ports);
  return !ports.includes(8082);
});
await proxy.updateRoutes(routesWithout8082);

Performance Considerations

Port Range Expansion

When using large port ranges, SmartProxy uses internal caching to optimize performance. For example, a range like { from: 1000, to: 2000 } is expanded only once and then cached for future use.

Port Range Validation

The system automatically validates port ranges to ensure:

  1. Port numbers are within the valid range (1-65535)
  2. The "from" value is not greater than the "to" value in range specifications
  3. Port ranges do not contain duplicate entries

Invalid port ranges will be logged as warnings and skipped during configuration.

Configuration Recipes

Global Port Range

Listen on a large range of ports and forward to the same ports on a backend:

{
  name: 'Global port range forwarding',
  match: {
    ports: [{ from: 8000, to: 9000 }]
  },
  action: {
    type: 'forward',
    target: {
      host: 'backend.example.com',
      port: 'preserve'
    }
  }
}

Domain-Specific Port Ranges

Different port ranges for different domain groups:

[
  {
    name: 'API port range',
    match: {
      ports: [{ from: 8000, to: 8099 }]
    },
    action: {
      type: 'forward',
      target: {
        host: 'api.backend.example.com',
        port: 'preserve'
      }
    }
  },
  {
    name: 'Admin port range',
    match: {
      ports: [{ from: 9000, to: 9099 }]
    },
    action: {
      type: 'forward',
      target: {
        host: 'admin.backend.example.com',
        port: 'preserve'
      }
    }
  }
]

Mixed Internal/External Port Forwarding

Forward specific high-numbered ports to standard ports on internal servers:

[
  {
    name: 'Web server forwarding',
    match: {
      ports: [8080, 8443]
    },
    action: {
      type: 'forward',
      target: {
        host: 'web.internal',
        port: (context) => context.port === 8080 ? 80 : 443
      }
    }
  },
  {
    name: 'Database forwarding',
    match: {
      ports: [15432]
    },
    action: {
      type: 'forward',
      target: {
        host: 'db.internal',
        port: 5432
      }
    }
  }
]

Debugging Port Configurations

When troubleshooting port forwarding issues, enable detailed logging:

const proxy = new SmartProxy({
  routes: [ /* your routes */ ],
  enableDetailedLogging: true
});

This will log:

  • Port configuration during startup
  • Port matching decisions during routing
  • Dynamic port function results
  • Connection details including source and target ports

Port Security Considerations

Restricting Ports

For security, you may want to restrict which ports can be accessed by specific clients:

{
  name: 'Restricted port range',
  match: {
    ports: [{ from: 8000, to: 9000 }],
    clientIp: ['10.0.0.0/8'] // Only internal network can access these ports
  },
  action: {
    type: 'forward',
    target: {
      host: 'internal.example.com',
      port: 'preserve'
    }
  }
}

Rate Limiting by Port

Apply different rate limits for different port ranges:

{
  name: 'API ports with rate limiting',
  match: {
    ports: [{ from: 8000, to: 8100 }]
  },
  action: {
    type: 'forward',
    target: {
      host: 'api.example.com',
      port: 'preserve'
    },
    security: {
      rateLimit: {
        enabled: true,
        maxRequests: 100,
        window: 60 // 60 seconds
      }
    }
  }
}

Best Practices

  1. Use Specific Port Ranges: Instead of large ranges (e.g., 1-65535), use specific ranges for specific purposes

  2. Prioritize Routes: When multiple routes could match, use the priority field to ensure the most specific route is matched first

  3. Name Your Routes: Use descriptive names to make debugging easier, especially when using port ranges

  4. Use Preserve Port Where Possible: Using port: 'preserve' is more efficient and easier to maintain than creating multiple specific mappings

  5. Limit Dynamic Port Functions: While powerful, complex port functions can be harder to debug; prefer simple map or math-based functions

  6. Use Port Variables: For complex setups, define your port ranges as variables for easier maintenance:

const API_PORTS = [{ from: 8000, to: 8099 }];
const ADMIN_PORTS = [{ from: 9000, to: 9099 }];

const routes = [
  {
    name: 'API Routes',
    match: { ports: API_PORTS, /* ... */ },
    // ...
  },
  {
    name: 'Admin Routes',
    match: { ports: ADMIN_PORTS, /* ... */ },
    // ...
  }
];