feat(http3): add automatic HTTP/3 route augmentation for qualifying HTTPS routes
This commit is contained in:
@@ -3,6 +3,6 @@
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@serve.zone/dcrouter',
|
||||
version: '11.5.1',
|
||||
version: '11.6.0',
|
||||
description: 'A multifaceted routing service handling mail and SMS delivery functions.'
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import { RadiusServer, type IRadiusServerConfig } from './radius/index.js';
|
||||
import { RemoteIngressManager, TunnelManager } from './remoteingress/index.js';
|
||||
import { RouteConfigManager, ApiTokenManager } from './config/index.js';
|
||||
import { SecurityLogger, ContentScanner, IPReputationChecker } from './security/index.js';
|
||||
import { type IHttp3Config, augmentRoutesWithHttp3 } from './http3/index.js';
|
||||
|
||||
export interface IDcRouterOptions {
|
||||
/** Base directory for all dcrouter data. Defaults to ~/.serve.zone/dcrouter */
|
||||
@@ -163,6 +164,14 @@ export interface IDcRouterOptions {
|
||||
* Remote Ingress configuration for edge tunnel nodes
|
||||
* Enables edge nodes to accept incoming connections and tunnel them to this DcRouter
|
||||
*/
|
||||
/**
|
||||
* HTTP/3 (QUIC) configuration for HTTPS routes.
|
||||
* Enabled by default — qualifying HTTPS routes on port 443 are automatically
|
||||
* augmented with QUIC/H3 fields. Set { enabled: false } to disable globally.
|
||||
* Individual routes can opt out via action.options.http3 = false.
|
||||
*/
|
||||
http3?: IHttp3Config;
|
||||
|
||||
/** Port for the OpsServer web UI (default: 3000) */
|
||||
opsServerPort?: number;
|
||||
|
||||
@@ -297,6 +306,7 @@ export class DcRouter {
|
||||
this.storageManager,
|
||||
() => this.getConstructorRoutes(),
|
||||
() => this.smartProxy,
|
||||
() => this.options.http3,
|
||||
);
|
||||
this.apiTokenManager = new ApiTokenManager(this.storageManager);
|
||||
await this.apiTokenManager.initialize();
|
||||
@@ -469,6 +479,13 @@ export class DcRouter {
|
||||
challengeHandlers.push(dns01Handler);
|
||||
}
|
||||
|
||||
// HTTP/3 augmentation (enabled by default unless explicitly disabled)
|
||||
if (this.options.http3?.enabled !== false) {
|
||||
const http3Config: IHttp3Config = { enabled: true, ...this.options.http3 };
|
||||
routes = augmentRoutesWithHttp3(routes, http3Config);
|
||||
logger.log('info', 'HTTP/3: Augmented qualifying HTTPS routes with QUIC/H3 configuration');
|
||||
}
|
||||
|
||||
// Cache constructor routes for RouteConfigManager
|
||||
this.constructorRoutes = [...routes];
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import type {
|
||||
IMergedRoute,
|
||||
IRouteWarning,
|
||||
} from '../../ts_interfaces/data/route-management.js';
|
||||
import { type IHttp3Config, augmentRouteWithHttp3 } from '../http3/index.js';
|
||||
|
||||
const ROUTES_PREFIX = '/config-api/routes/';
|
||||
const OVERRIDES_PREFIX = '/config-api/overrides/';
|
||||
@@ -20,6 +21,7 @@ export class RouteConfigManager {
|
||||
private storageManager: StorageManager,
|
||||
private getHardcodedRoutes: () => plugins.smartproxy.IRouteConfig[],
|
||||
private getSmartProxy: () => plugins.smartproxy.SmartProxy | undefined,
|
||||
private getHttp3Config?: () => IHttp3Config | undefined,
|
||||
) {}
|
||||
|
||||
/**
|
||||
@@ -258,10 +260,15 @@ export class RouteConfigManager {
|
||||
enabledRoutes.push(route);
|
||||
}
|
||||
|
||||
// Add enabled programmatic routes
|
||||
// Add enabled programmatic routes (with HTTP/3 augmentation if enabled)
|
||||
const http3Config = this.getHttp3Config?.();
|
||||
for (const stored of this.storedRoutes.values()) {
|
||||
if (stored.enabled) {
|
||||
enabledRoutes.push(stored.route);
|
||||
if (http3Config && http3Config.enabled !== false) {
|
||||
enabledRoutes.push(augmentRouteWithHttp3(stored.route, { enabled: true, ...http3Config }));
|
||||
} else {
|
||||
enabledRoutes.push(stored.route);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
153
ts/http3/http3-route-augmentation.ts
Normal file
153
ts/http3/http3-route-augmentation.ts
Normal file
@@ -0,0 +1,153 @@
|
||||
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));
|
||||
}
|
||||
1
ts/http3/index.ts
Normal file
1
ts/http3/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './http3-route-augmentation.js';
|
||||
@@ -14,6 +14,9 @@ export * from './radius/index.js';
|
||||
// Remote Ingress module
|
||||
export * from './remoteingress/index.js';
|
||||
|
||||
// HTTP/3 module
|
||||
export type { IHttp3Config } from './http3/index.js';
|
||||
|
||||
export const runCli = async () => {
|
||||
let options: import('./classes.dcrouter.js').IDcRouterOptions = {};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user