@push.rocks/smartproxy
Version:
A powerful proxy package with unified route-based configuration for high traffic management. Features include SSL/TLS support, flexible routing patterns, WebSocket handling, advanced security options, and automatic ACME certificate management.
453 lines • 36.6 kB
JavaScript
import * as plugins from '../../plugins.js';
/**
* The RouteManager handles all routing decisions based on connections and attributes
*/
export class RouteManager extends plugins.EventEmitter {
constructor(options) {
super();
this.routes = [];
this.portMap = new Map();
/**
* Memoization cache for expanded port ranges
*/
this.portRangeCache = new Map();
// Store options
this.options = options;
// Initialize routes from either source
this.updateRoutes(this.options.routes);
}
/**
* Update routes with new configuration
*/
updateRoutes(routes = []) {
// Sort routes by priority (higher first)
this.routes = [...(routes || [])].sort((a, b) => {
const priorityA = a.priority ?? 0;
const priorityB = b.priority ?? 0;
return priorityB - priorityA;
});
// Rebuild port mapping for fast lookups
this.rebuildPortMap();
}
/**
* Rebuild the port mapping for fast lookups
* Also logs information about the ports being listened on
*/
rebuildPortMap() {
this.portMap.clear();
this.portRangeCache.clear(); // Clear cache when rebuilding
// Track ports for logging
const portToRoutesMap = new Map();
for (const route of this.routes) {
const ports = this.expandPortRange(route.match.ports);
// Skip if no ports were found
if (ports.length === 0) {
console.warn(`Route ${route.name || 'unnamed'} has no valid ports to listen on`);
continue;
}
for (const port of ports) {
// Add to portMap for routing
if (!this.portMap.has(port)) {
this.portMap.set(port, []);
}
this.portMap.get(port).push(route);
// Add to tracking for logging
if (!portToRoutesMap.has(port)) {
portToRoutesMap.set(port, []);
}
portToRoutesMap.get(port).push(route.name || 'unnamed');
}
}
// Log summary of ports and routes
const totalPorts = this.portMap.size;
const totalRoutes = this.routes.length;
console.log(`Route manager configured with ${totalRoutes} routes across ${totalPorts} ports`);
// Log port details if detailed logging is enabled
const enableDetailedLogging = this.options.enableDetailedLogging;
if (enableDetailedLogging) {
for (const [port, routes] of this.portMap.entries()) {
console.log(`Port ${port}: ${routes.length} routes (${portToRoutesMap.get(port).join(', ')})`);
}
}
}
/**
* Expand a port range specification into an array of individual ports
* Uses caching to improve performance for frequently used port ranges
*
* @public - Made public to allow external code to interpret port ranges
*/
expandPortRange(portRange) {
// For simple number, return immediately
if (typeof portRange === 'number') {
return [portRange];
}
// Create a cache key for this port range
const cacheKey = JSON.stringify(portRange);
// Check if we have a cached result
if (this.portRangeCache.has(cacheKey)) {
return this.portRangeCache.get(cacheKey);
}
// Process the port range
let result = [];
if (Array.isArray(portRange)) {
// Handle array of port objects or numbers
result = portRange.flatMap(item => {
if (typeof item === 'number') {
return [item];
}
else if (typeof item === 'object' && 'from' in item && 'to' in item) {
// Handle port range object - check valid range
if (item.from > item.to) {
console.warn(`Invalid port range: from (${item.from}) > to (${item.to})`);
return [];
}
// Handle port range object
const ports = [];
for (let p = item.from; p <= item.to; p++) {
ports.push(p);
}
return ports;
}
return [];
});
}
// Cache the result
this.portRangeCache.set(cacheKey, result);
return result;
}
/**
* Get all ports that should be listened on
* This method automatically infers all required ports from route configurations
*/
getListeningPorts() {
// Return the unique set of ports from all routes
return Array.from(this.portMap.keys());
}
/**
* Get all routes for a given port
*/
getRoutesForPort(port) {
return this.portMap.get(port) || [];
}
/**
* Get all routes
*/
getAllRoutes() {
return [...this.routes];
}
/**
* Test if a pattern matches a domain using glob matching
*/
matchDomain(pattern, domain) {
// Convert glob pattern to regex
const regexPattern = pattern
.replace(/\./g, '\\.') // Escape dots
.replace(/\*/g, '.*'); // Convert * to .*
const regex = new RegExp(`^${regexPattern}$`, 'i');
return regex.test(domain);
}
/**
* Match a domain against all patterns in a route
*/
matchRouteDomain(route, domain) {
if (!route.match.domains) {
// If no domains specified, match all domains
return true;
}
const patterns = Array.isArray(route.match.domains)
? route.match.domains
: [route.match.domains];
return patterns.some(pattern => this.matchDomain(pattern, domain));
}
/**
* Check if a client IP is allowed by a route's security settings
* @deprecated Security is now checked in route-connection-handler.ts after route matching
*/
isClientIpAllowed(route, clientIp) {
const security = route.security;
if (!security) {
return true; // No security settings means allowed
}
// Check blocked IPs first
if (security.ipBlockList && security.ipBlockList.length > 0) {
for (const pattern of security.ipBlockList) {
if (this.matchIpPattern(pattern, clientIp)) {
return false; // IP is blocked
}
}
}
// If there are allowed IPs, check them
if (security.ipAllowList && security.ipAllowList.length > 0) {
for (const pattern of security.ipAllowList) {
if (this.matchIpPattern(pattern, clientIp)) {
return true; // IP is allowed
}
}
return false; // IP not in allowed list
}
// No allowed IPs specified, so IP is allowed
return true;
}
/**
* Match an IP against a pattern
*/
matchIpPattern(pattern, ip) {
// Normalize IPv6-mapped IPv4 addresses
const normalizedIp = ip.startsWith('::ffff:') ? ip.substring(7) : ip;
const normalizedPattern = pattern.startsWith('::ffff:') ? pattern.substring(7) : pattern;
// Handle exact match with normalized addresses
if (pattern === ip || normalizedPattern === normalizedIp ||
pattern === normalizedIp || normalizedPattern === ip) {
return true;
}
// Handle CIDR notation (e.g., 192.168.1.0/24)
if (pattern.includes('/')) {
return this.matchIpCidr(pattern, normalizedIp) ||
(normalizedPattern !== pattern && this.matchIpCidr(normalizedPattern, normalizedIp));
}
// Handle glob pattern (e.g., 192.168.1.*)
if (pattern.includes('*')) {
const regexPattern = pattern.replace(/\./g, '\\.').replace(/\*/g, '.*');
const regex = new RegExp(`^${regexPattern}$`);
if (regex.test(ip) || regex.test(normalizedIp)) {
return true;
}
// If pattern was normalized, also test with normalized pattern
if (normalizedPattern !== pattern) {
const normalizedRegexPattern = normalizedPattern.replace(/\./g, '\\.').replace(/\*/g, '.*');
const normalizedRegex = new RegExp(`^${normalizedRegexPattern}$`);
return normalizedRegex.test(ip) || normalizedRegex.test(normalizedIp);
}
}
return false;
}
/**
* Match an IP against a CIDR pattern
*/
matchIpCidr(cidr, ip) {
try {
// In a real implementation, you'd use a proper IP library
// This is a simplified implementation
const [subnet, bits] = cidr.split('/');
const mask = parseInt(bits, 10);
// Normalize IPv6-mapped IPv4 addresses
const normalizedIp = ip.startsWith('::ffff:') ? ip.substring(7) : ip;
const normalizedSubnet = subnet.startsWith('::ffff:') ? subnet.substring(7) : subnet;
// Convert IP addresses to numeric values
const ipNum = this.ipToNumber(normalizedIp);
const subnetNum = this.ipToNumber(normalizedSubnet);
// Calculate subnet mask
const maskNum = ~(2 ** (32 - mask) - 1);
// Check if IP is in subnet
return (ipNum & maskNum) === (subnetNum & maskNum);
}
catch (e) {
console.error(`Error matching IP ${ip} against CIDR ${cidr}:`, e);
return false;
}
}
/**
* Convert an IP address to a numeric value
*/
ipToNumber(ip) {
// Normalize IPv6-mapped IPv4 addresses
const normalizedIp = ip.startsWith('::ffff:') ? ip.substring(7) : ip;
const parts = normalizedIp.split('.').map(part => parseInt(part, 10));
return (parts[0] << 24) | (parts[1] << 16) | (parts[2] << 8) | parts[3];
}
/**
* Find the matching route for a connection
*/
findMatchingRoute(options) {
const { port, domain, clientIp, path, tlsVersion, skipDomainCheck } = options;
// Get all routes for this port
const routesForPort = this.getRoutesForPort(port);
// Find the first matching route based on priority order
for (const route of routesForPort) {
// Check domain match
// If the route has domain restrictions and we have a domain to check
if (route.match.domains && !skipDomainCheck) {
// If no domain was provided (non-TLS or no SNI), this route doesn't match
if (!domain) {
continue;
}
// If domain is provided but doesn't match the route's domains, skip
if (!this.matchRouteDomain(route, domain)) {
continue;
}
}
// If route has no domain restrictions, it matches all domains
// If skipDomainCheck is true, we skip domain validation for HTTP connections
// Check path match if specified in both route and request
if (path && route.match.path) {
if (!this.matchPath(route.match.path, path)) {
continue;
}
}
// Check client IP match
if (route.match.clientIp && !route.match.clientIp.some(pattern => this.matchIpPattern(pattern, clientIp))) {
continue;
}
// Check TLS version match
if (tlsVersion && route.match.tlsVersion &&
!route.match.tlsVersion.includes(tlsVersion)) {
continue;
}
// All checks passed, this route matches
// NOTE: Security is checked AFTER route matching in route-connection-handler.ts
return { route };
}
return null;
}
/**
* Match a path against a pattern
*/
matchPath(pattern, path) {
// Convert the glob pattern to a regex
const regexPattern = pattern
.replace(/\./g, '\\.') // Escape dots
.replace(/\*/g, '.*') // Convert * to .*
.replace(/\//g, '\\/'); // Escape slashes
const regex = new RegExp(`^${regexPattern}$`);
return regex.test(path);
}
/**
* Domain-based configuration methods have been removed
* as part of the migration to pure route-based configuration
*/
/**
* Validate the route configuration and return any warnings
*/
validateConfiguration() {
const warnings = [];
const duplicatePorts = new Map();
// Check for routes with the same exact match criteria
for (let i = 0; i < this.routes.length; i++) {
for (let j = i + 1; j < this.routes.length; j++) {
const route1 = this.routes[i];
const route2 = this.routes[j];
// Check if route match criteria are the same
if (this.areMatchesSimilar(route1.match, route2.match)) {
warnings.push(`Routes "${route1.name || i}" and "${route2.name || j}" have similar match criteria. ` +
`The route with higher priority (${Math.max(route1.priority || 0, route2.priority || 0)}) will be used.`);
}
}
}
// Check for routes that may never be matched due to priority
for (let i = 0; i < this.routes.length; i++) {
const route = this.routes[i];
const higherPriorityRoutes = this.routes.filter(r => (r.priority || 0) > (route.priority || 0));
for (const higherRoute of higherPriorityRoutes) {
if (this.isRouteShadowed(route, higherRoute)) {
warnings.push(`Route "${route.name || i}" may never be matched because it is shadowed by ` +
`higher priority route "${higherRoute.name || 'unnamed'}"`);
break;
}
}
}
return warnings;
}
/**
* Check if two route matches are similar (potential conflict)
*/
areMatchesSimilar(match1, match2) {
// Check port overlap
const ports1 = new Set(this.expandPortRange(match1.ports));
const ports2 = new Set(this.expandPortRange(match2.ports));
let havePortOverlap = false;
for (const port of ports1) {
if (ports2.has(port)) {
havePortOverlap = true;
break;
}
}
if (!havePortOverlap) {
return false;
}
// Check domain overlap
if (match1.domains && match2.domains) {
const domains1 = Array.isArray(match1.domains) ? match1.domains : [match1.domains];
const domains2 = Array.isArray(match2.domains) ? match2.domains : [match2.domains];
// Check if any domain pattern from match1 could match any from match2
let haveDomainOverlap = false;
for (const domain1 of domains1) {
for (const domain2 of domains2) {
if (domain1 === domain2 ||
(domain1.includes('*') || domain2.includes('*'))) {
haveDomainOverlap = true;
break;
}
}
if (haveDomainOverlap)
break;
}
if (!haveDomainOverlap) {
return false;
}
}
else if (match1.domains || match2.domains) {
// One has domains, the other doesn't - they could overlap
// The one with domains is more specific, so it's not exactly a conflict
return false;
}
// Check path overlap
if (match1.path && match2.path) {
// This is a simplified check - in a real implementation,
// you'd need to check if the path patterns could match the same paths
return match1.path === match2.path ||
match1.path.includes('*') ||
match2.path.includes('*');
}
else if (match1.path || match2.path) {
// One has a path, the other doesn't
return false;
}
// If we get here, the matches have significant overlap
return true;
}
/**
* Check if a route is completely shadowed by a higher priority route
*/
isRouteShadowed(route, higherPriorityRoute) {
// If they don't have similar match criteria, no shadowing occurs
if (!this.areMatchesSimilar(route.match, higherPriorityRoute.match)) {
return false;
}
// If higher priority route has more specific criteria, no shadowing
if (this.isRouteMoreSpecific(higherPriorityRoute.match, route.match)) {
return false;
}
// If higher priority route is equally or less specific but has higher priority,
// it shadows the lower priority route
return true;
}
/**
* Check if route1 is more specific than route2
*/
isRouteMoreSpecific(match1, match2) {
// Check if match1 has more specific criteria
let match1Points = 0;
let match2Points = 0;
// Path is the most specific
if (match1.path)
match1Points += 3;
if (match2.path)
match2Points += 3;
// Domain is next most specific
if (match1.domains)
match1Points += 2;
if (match2.domains)
match2Points += 2;
// Client IP and TLS version are least specific
if (match1.clientIp)
match1Points += 1;
if (match2.clientIp)
match2Points += 1;
if (match1.tlsVersion)
match1Points += 1;
if (match2.tlsVersion)
match2Points += 1;
return match1Points > match2Points;
}
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicm91dGUtbWFuYWdlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3RzL3Byb3hpZXMvc21hcnQtcHJveHkvcm91dGUtbWFuYWdlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLGtCQUFrQixDQUFDO0FBb0I1Qzs7R0FFRztBQUNILE1BQU0sT0FBTyxZQUFhLFNBQVEsT0FBTyxDQUFDLFlBQVk7SUFLcEQsWUFBWSxPQUEyQjtRQUNyQyxLQUFLLEVBQUUsQ0FBQztRQUxGLFdBQU0sR0FBbUIsRUFBRSxDQUFDO1FBQzVCLFlBQU8sR0FBZ0MsSUFBSSxHQUFHLEVBQUUsQ0FBQztRQWlJekQ7O1dBRUc7UUFDSyxtQkFBYyxHQUEwQixJQUFJLEdBQUcsRUFBRSxDQUFDO1FBOUh4RCxnQkFBZ0I7UUFDaEIsSUFBSSxDQUFDLE9BQU8sR0FBRyxPQUFPLENBQUM7UUFFdkIsdUNBQXVDO1FBQ3ZDLElBQUksQ0FBQyxZQUFZLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxNQUFNLENBQUMsQ0FBQztJQUN6QyxDQUFDO0lBRUQ7O09BRUc7SUFDSSxZQUFZLENBQUMsU0FBeUIsRUFBRTtRQUM3Qyx5Q0FBeUM7UUFDekMsSUFBSSxDQUFDLE1BQU0sR0FBRyxDQUFDLEdBQUcsQ0FBQyxNQUFNLElBQUksRUFBRSxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxFQUFFLEVBQUU7WUFDOUMsTUFBTSxTQUFTLEdBQUcsQ0FBQyxDQUFDLFFBQVEsSUFBSSxDQUFDLENBQUM7WUFDbEMsTUFBTSxTQUFTLEdBQUcsQ0FBQyxDQUFDLFFBQVEsSUFBSSxDQUFDLENBQUM7WUFDbEMsT0FBTyxTQUFTLEdBQUcsU0FBUyxDQUFDO1FBQy9CLENBQUMsQ0FBQyxDQUFDO1FBRUgsd0NBQXdDO1FBQ3hDLElBQUksQ0FBQyxjQUFjLEVBQUUsQ0FBQztJQUN4QixDQUFDO0lBRUQ7OztPQUdHO0lB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