import type * as plugins from '../plugins.js'; /** * Configuration for HTTP/3 (QUIC) route augmentation. * HTTP/3 is enabled by default on all qualifying HTTPS routes. */ export interface IHttp3Config { /** Enable HTTP/3 augmentation on qualifying routes (default: true) */ enabled?: boolean; /** QUIC-specific settings applied to all augmented routes */ quicSettings?: { /** QUIC connection idle timeout in ms (default: 30000) */ maxIdleTimeout?: number; /** Max concurrent bidirectional streams per connection (default: 100) */ maxConcurrentBidiStreams?: number; /** Max concurrent unidirectional streams per connection (default: 100) */ maxConcurrentUniStreams?: number; /** Initial congestion window size in bytes */ initialCongestionWindow?: number; }; /** Alt-Svc header settings */ altSvc?: { /** Port advertised in Alt-Svc header (default: same as listening port) */ port?: number; /** Max age for Alt-Svc advertisement in seconds (default: 86400) */ maxAge?: number; }; /** UDP session settings */ udpSettings?: { /** Idle timeout for UDP sessions in ms (default: 60000) */ sessionTimeout?: number; /** Max concurrent UDP sessions per source IP (default: 1000) */ maxSessionsPerIP?: number; /** Max accepted datagram size in bytes (default: 65535) */ maxDatagramSize?: number; }; } type TPortRange = plugins.smartproxy.IRouteConfig['match']['ports']; /** * Check whether a TPortRange includes port 443. */ function portRangeIncludes443(ports: TPortRange): boolean { if (typeof ports === 'number') return ports === 443; if (Array.isArray(ports)) { return ports.some((p) => { if (typeof p === 'number') return p === 443; return p.from <= 443 && p.to >= 443; }); } return false; } /** * Check if a route name indicates an email route that should not get HTTP/3. */ function isEmailRoute(route: plugins.smartproxy.IRouteConfig): boolean { const name = route.name?.toLowerCase() || ''; return ( name.startsWith('smtp-') || name.startsWith('submission-') || name.startsWith('smtps-') || name.startsWith('email-') ); } /** * Determine if a route qualifies for HTTP/3 augmentation. */ export function routeQualifiesForHttp3( route: plugins.smartproxy.IRouteConfig, globalConfig: IHttp3Config, ): boolean { // Check global enable + per-route override const globalEnabled = globalConfig.enabled !== false; // default true const perRouteOverride = route.action.options?.http3; // If per-route explicitly set, use that; otherwise use global const shouldAugment = perRouteOverride !== undefined ? perRouteOverride : globalEnabled; if (!shouldAugment) return false; // Must be forward type if (route.action.type !== 'forward') return false; // Must include port 443 if (!portRangeIncludes443(route.match.ports)) return false; // Must have TLS if (!route.action.tls) return false; // Skip email routes if (isEmailRoute(route)) return false; // Skip if already configured with transport 'all' or 'udp' if (route.match.transport === 'all' || route.match.transport === 'udp') return false; // Skip if already has QUIC config if (route.action.udp?.quic) return false; return true; } /** * Augment a single route with HTTP/3 fields. * Returns a new route object (does not mutate the original). */ export function augmentRouteWithHttp3( route: plugins.smartproxy.IRouteConfig, config: IHttp3Config, ): plugins.smartproxy.IRouteConfig { if (!routeQualifiesForHttp3(route, config)) { return route; } return { ...route, match: { ...route.match, transport: 'all' as const, }, action: { ...route.action, udp: { ...(route.action.udp || {}), sessionTimeout: config.udpSettings?.sessionTimeout, maxSessionsPerIP: config.udpSettings?.maxSessionsPerIP, maxDatagramSize: config.udpSettings?.maxDatagramSize, quic: { enableHttp3: true, maxIdleTimeout: config.quicSettings?.maxIdleTimeout, maxConcurrentBidiStreams: config.quicSettings?.maxConcurrentBidiStreams, maxConcurrentUniStreams: config.quicSettings?.maxConcurrentUniStreams, altSvcPort: config.altSvc?.port, altSvcMaxAge: config.altSvc?.maxAge ?? 86400, initialCongestionWindow: config.quicSettings?.initialCongestionWindow, }, }, }, }; } /** * Augment all qualifying routes in an array. * Returns a new array (does not mutate originals). */ export function augmentRoutesWithHttp3( routes: plugins.smartproxy.IRouteConfig[], config: IHttp3Config, ): plugins.smartproxy.IRouteConfig[] { return routes.map((route) => augmentRouteWithHttp3(route, config)); }