feat(smart-proxy): calculate when SNI is required for TLS routing and allow session tickets for single-target passthrough routes; add tests, docs, and npm metadata updates
This commit is contained in:
@@ -69,6 +69,58 @@ export class RouteConnectionHandler {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if SNI is required for routing decisions on this port.
|
||||
*
|
||||
* SNI is REQUIRED when:
|
||||
* - Multiple routes exist on this port (need SNI to pick correct route)
|
||||
* - Route has dynamic target function (needs ctx.domain)
|
||||
* - Route has specific domain restriction (strict validation)
|
||||
*
|
||||
* SNI is NOT required when:
|
||||
* - TLS termination mode (HttpProxy handles session resumption)
|
||||
* - Single route with static target and no domain restriction (or wildcard)
|
||||
*/
|
||||
private calculateSniRequirement(port: number): boolean {
|
||||
const routesOnPort = this.smartProxy.routeManager.getRoutesForPort(port);
|
||||
|
||||
// No routes = no SNI requirement (will fail routing anyway)
|
||||
if (routesOnPort.length === 0) return false;
|
||||
|
||||
// Check if any route terminates TLS - if so, SNI not required
|
||||
// (HttpProxy handles session resumption internally)
|
||||
const hasTermination = routesOnPort.some(route =>
|
||||
route.action.tls?.mode === 'terminate' ||
|
||||
route.action.tls?.mode === 'terminate-and-reencrypt'
|
||||
);
|
||||
if (hasTermination) return false;
|
||||
|
||||
// Multiple routes = need SNI to pick the correct route
|
||||
if (routesOnPort.length > 1) return true;
|
||||
|
||||
// Single route - check if it needs SNI for validation or routing
|
||||
const route = routesOnPort[0];
|
||||
|
||||
// Dynamic host selection requires SNI (function receives ctx.domain)
|
||||
const hasDynamicTarget = route.action.targets?.some(t => typeof t.host === 'function');
|
||||
if (hasDynamicTarget) return true;
|
||||
|
||||
// Specific domain restriction requires SNI for strict validation
|
||||
const hasSpecificDomain = route.match.domains && !this.isWildcardOnly(route.match.domains);
|
||||
if (hasSpecificDomain) return true;
|
||||
|
||||
// Single route, static target(s), no domain restriction = SNI not required
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if domains config is wildcard-only (matches everything)
|
||||
*/
|
||||
private isWildcardOnly(domains: string | string[]): boolean {
|
||||
const domainList = Array.isArray(domains) ? domains : [domains];
|
||||
return domainList.length === 1 && domainList[0] === '*';
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a new incoming connection
|
||||
*/
|
||||
@@ -201,19 +253,10 @@ export class RouteConnectionHandler {
|
||||
route.action.tls.mode === 'passthrough');
|
||||
});
|
||||
|
||||
// Auto-calculate session ticket handling based on route configuration
|
||||
// If any route on this port terminates TLS, allow session tickets (HttpProxy handles resumption)
|
||||
// Otherwise, block session tickets (need SNI for passthrough routing)
|
||||
const hasTlsTermination = allRoutes.some(route => {
|
||||
const matchesPort = this.smartProxy.routeManager.getRoutesForPort(localPort).includes(route);
|
||||
|
||||
return matchesPort &&
|
||||
route.action.type === 'forward' &&
|
||||
route.action.tls &&
|
||||
(route.action.tls.mode === 'terminate' ||
|
||||
route.action.tls.mode === 'terminate-and-reencrypt');
|
||||
});
|
||||
const allowSessionTicket = hasTlsTermination;
|
||||
// Smart SNI requirement calculation
|
||||
// Determines if we need SNI for routing decisions on this port
|
||||
const needsSniForRouting = this.calculateSniRequirement(localPort);
|
||||
const allowSessionTicket = !needsSniForRouting;
|
||||
|
||||
// If no routes require TLS handling and it's not port 443, route immediately
|
||||
if (!needsTlsHandling && localPort !== 443) {
|
||||
|
||||
Reference in New Issue
Block a user