203 lines
5.5 KiB
TypeScript
203 lines
5.5 KiB
TypeScript
/**
|
|
* NFTables Route Helper Functions
|
|
*
|
|
* This module provides utility functions for creating NFTables-based route configurations
|
|
* for high-performance packet forwarding at the kernel level.
|
|
*/
|
|
|
|
import type { IRouteConfig, IRouteMatch, IRouteAction, TPortRange } from '../../models/route-types.js';
|
|
import { createHttpToHttpsRedirect } from './https-helpers.js';
|
|
|
|
/**
|
|
* Create an NFTables-based route for high-performance packet forwarding
|
|
* @param nameOrDomains Name or domain(s) to match
|
|
* @param target Target host and port
|
|
* @param options Additional route options
|
|
* @returns Route configuration object
|
|
*/
|
|
export function createNfTablesRoute(
|
|
nameOrDomains: string | string[],
|
|
target: { host: string; port: number | 'preserve' },
|
|
options: {
|
|
ports?: TPortRange;
|
|
protocol?: 'tcp' | 'udp' | 'all';
|
|
preserveSourceIP?: boolean;
|
|
ipAllowList?: string[];
|
|
ipBlockList?: string[];
|
|
maxRate?: string;
|
|
priority?: number;
|
|
useTls?: boolean;
|
|
tableName?: string;
|
|
useIPSets?: boolean;
|
|
useAdvancedNAT?: boolean;
|
|
} = {}
|
|
): IRouteConfig {
|
|
// Determine if this is a name or domain
|
|
let name: string;
|
|
let domains: string | string[] | undefined;
|
|
|
|
if (Array.isArray(nameOrDomains) || (typeof nameOrDomains === 'string' && nameOrDomains.includes('.'))) {
|
|
domains = nameOrDomains;
|
|
name = Array.isArray(nameOrDomains) ? nameOrDomains[0] : nameOrDomains;
|
|
} else {
|
|
name = nameOrDomains;
|
|
domains = undefined; // No domains
|
|
}
|
|
|
|
// Create route match
|
|
const match: IRouteMatch = {
|
|
domains,
|
|
ports: options.ports || 80
|
|
};
|
|
|
|
// Create route action
|
|
const action: IRouteAction = {
|
|
type: 'forward',
|
|
targets: [{
|
|
host: target.host,
|
|
port: target.port
|
|
}],
|
|
forwardingEngine: 'nftables',
|
|
nftables: {
|
|
protocol: options.protocol || 'tcp',
|
|
preserveSourceIP: options.preserveSourceIP,
|
|
maxRate: options.maxRate,
|
|
priority: options.priority,
|
|
tableName: options.tableName,
|
|
useIPSets: options.useIPSets,
|
|
useAdvancedNAT: options.useAdvancedNAT
|
|
}
|
|
};
|
|
|
|
// Add TLS options if needed
|
|
if (options.useTls) {
|
|
action.tls = {
|
|
mode: 'passthrough'
|
|
};
|
|
}
|
|
|
|
// Create the route config
|
|
const routeConfig: IRouteConfig = {
|
|
name,
|
|
match,
|
|
action
|
|
};
|
|
|
|
// Add security if allowed or blocked IPs are specified
|
|
if (options.ipAllowList?.length || options.ipBlockList?.length) {
|
|
routeConfig.security = {
|
|
ipAllowList: options.ipAllowList,
|
|
ipBlockList: options.ipBlockList
|
|
};
|
|
}
|
|
|
|
return routeConfig;
|
|
}
|
|
|
|
/**
|
|
* Create an NFTables-based TLS termination route
|
|
* @param nameOrDomains Name or domain(s) to match
|
|
* @param target Target host and port
|
|
* @param options Additional route options
|
|
* @returns Route configuration object
|
|
*/
|
|
export function createNfTablesTerminateRoute(
|
|
nameOrDomains: string | string[],
|
|
target: { host: string; port: number | 'preserve' },
|
|
options: {
|
|
ports?: TPortRange;
|
|
protocol?: 'tcp' | 'udp' | 'all';
|
|
preserveSourceIP?: boolean;
|
|
ipAllowList?: string[];
|
|
ipBlockList?: string[];
|
|
maxRate?: string;
|
|
priority?: number;
|
|
tableName?: string;
|
|
useIPSets?: boolean;
|
|
useAdvancedNAT?: boolean;
|
|
certificate?: 'auto' | { key: string; cert: string };
|
|
} = {}
|
|
): IRouteConfig {
|
|
// Create basic NFTables route
|
|
const route = createNfTablesRoute(
|
|
nameOrDomains,
|
|
target,
|
|
{
|
|
...options,
|
|
ports: options.ports || 443,
|
|
useTls: false
|
|
}
|
|
);
|
|
|
|
// Set TLS termination
|
|
route.action.tls = {
|
|
mode: 'terminate',
|
|
certificate: options.certificate || 'auto'
|
|
};
|
|
|
|
return route;
|
|
}
|
|
|
|
/**
|
|
* Create a complete NFTables-based HTTPS setup with HTTP redirect
|
|
* @param nameOrDomains Name or domain(s) to match
|
|
* @param target Target host and port
|
|
* @param options Additional route options
|
|
* @returns Array of two route configurations (HTTPS and HTTP redirect)
|
|
*/
|
|
export function createCompleteNfTablesHttpsServer(
|
|
nameOrDomains: string | string[],
|
|
target: { host: string; port: number | 'preserve' },
|
|
options: {
|
|
httpPort?: TPortRange;
|
|
httpsPort?: TPortRange;
|
|
protocol?: 'tcp' | 'udp' | 'all';
|
|
preserveSourceIP?: boolean;
|
|
ipAllowList?: string[];
|
|
ipBlockList?: string[];
|
|
maxRate?: string;
|
|
priority?: number;
|
|
tableName?: string;
|
|
useIPSets?: boolean;
|
|
useAdvancedNAT?: boolean;
|
|
certificate?: 'auto' | { key: string; cert: string };
|
|
} = {}
|
|
): IRouteConfig[] {
|
|
// Create the HTTPS route using NFTables
|
|
const httpsRoute = createNfTablesTerminateRoute(
|
|
nameOrDomains,
|
|
target,
|
|
{
|
|
...options,
|
|
ports: options.httpsPort || 443
|
|
}
|
|
);
|
|
|
|
// Determine the domain(s) for HTTP redirect
|
|
const domains = typeof nameOrDomains === 'string' && !nameOrDomains.includes('.')
|
|
? undefined
|
|
: nameOrDomains;
|
|
|
|
// Extract the HTTPS port for the redirect destination
|
|
const httpsPort = typeof options.httpsPort === 'number'
|
|
? options.httpsPort
|
|
: Array.isArray(options.httpsPort) && typeof options.httpsPort[0] === 'number'
|
|
? options.httpsPort[0]
|
|
: 443;
|
|
|
|
// Create the HTTP redirect route (this uses standard forwarding, not NFTables)
|
|
const httpRedirectRoute = createHttpToHttpsRedirect(
|
|
domains as any, // Type cast needed since domains can be undefined now
|
|
httpsPort,
|
|
{
|
|
match: {
|
|
ports: options.httpPort || 80,
|
|
domains: domains as any // Type cast needed since domains can be undefined now
|
|
},
|
|
name: `HTTP to HTTPS Redirect for ${Array.isArray(domains) ? domains.join(', ') : domains || 'all domains'}`
|
|
}
|
|
);
|
|
|
|
return [httpsRoute, httpRedirectRoute];
|
|
}
|