feat(nftables): move NFTables forwarding management from the Rust engine to @push.rocks/smartnftables

This commit is contained in:
2026-03-26 13:11:57 +00:00
parent c0e432fd9b
commit a6ffa24e36
22 changed files with 149 additions and 650 deletions

View File

@@ -3,6 +3,6 @@
*/
export const commitinfo = {
name: '@push.rocks/smartproxy',
version: '26.2.4',
version: '26.3.0',
description: '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.'
}

View File

@@ -19,12 +19,14 @@ export { tsclass };
import * as smartcrypto from '@push.rocks/smartcrypto';
import * as smartlog from '@push.rocks/smartlog';
import * as smartlogDestinationLocal from '@push.rocks/smartlog/destination-local';
import * as smartnftables from '@push.rocks/smartnftables';
import * as smartrust from '@push.rocks/smartrust';
export {
smartcrypto,
smartlog,
smartlogDestinationLocal,
smartnftables,
smartrust,
};

View File

@@ -15,7 +15,6 @@ type TSmartProxyCommands = {
renewCertificate: { params: { routeName: string }; result: void };
getCertificateStatus: { params: { routeName: string }; result: any };
getListeningPorts: { params: Record<string, never>; result: { ports: number[] } };
getNftablesStatus: { params: Record<string, never>; result: any };
setSocketHandlerRelay: { params: { socketPath: string }; result: void };
addListeningPort: { params: { port: number }; result: void };
removeListeningPort: { params: { port: number }; result: void };
@@ -159,10 +158,6 @@ export class RustProxyBridge extends plugins.EventEmitter {
return result?.ports ?? [];
}
public async getNftablesStatus(): Promise<any> {
return this.bridge.sendCommand('getNftablesStatus', {} as Record<string, never>);
}
public async setSocketHandlerRelay(socketPath: string): Promise<void> {
await this.bridge.sendCommand('setSocketHandlerRelay', { socketPath });
}

View File

@@ -23,8 +23,8 @@ import type { IMetrics } from './models/metrics-types.js';
/**
* SmartProxy - Rust-backed proxy engine with TypeScript configuration API.
*
* All networking (TCP, TLS, HTTP reverse proxy, connection management, security,
* NFTables) is handled by the Rust binary. TypeScript is only:
* All networking (TCP, TLS, HTTP reverse proxy, connection management, security)
* is handled by the Rust binary. TypeScript is only:
* - The npm module interface (types, route helpers)
* - The thin IPC wrapper (this class)
* - Socket-handler callback relay (for JS-defined handlers)
@@ -39,6 +39,7 @@ export class SmartProxy extends plugins.EventEmitter {
private socketHandlerServer: SocketHandlerServer | null = null;
private datagramHandlerServer: DatagramHandlerServer | null = null;
private metricsAdapter: RustMetricsAdapter;
private nftablesManager: InstanceType<typeof plugins.smartnftables.SmartNftables> | null = null;
private routeUpdateLock: Mutex;
private stopping = false;
private certProvisionPromise: Promise<void> | null = null;
@@ -211,6 +212,9 @@ export class SmartProxy extends plugins.EventEmitter {
}
}
// Apply NFTables rules for routes using nftables forwarding engine
await this.applyNftablesRules(this.settings.routes);
// Start metrics polling BEFORE cert provisioning — the Rust engine is already
// running and accepting connections, so metrics should be available immediately.
// Cert provisioning can hang indefinitely (e.g. DNS-01 ACME timeouts) and must
@@ -238,6 +242,12 @@ export class SmartProxy extends plugins.EventEmitter {
this.certProvisionPromise = null;
}
// Clean up NFTables rules
if (this.nftablesManager) {
await this.nftablesManager.cleanup();
this.nftablesManager = null;
}
// Stop metrics polling
this.metricsAdapter.stopPolling();
@@ -319,6 +329,13 @@ export class SmartProxy extends plugins.EventEmitter {
this.datagramHandlerServer = null;
}
// Update NFTables rules
if (this.nftablesManager) {
await this.nftablesManager.cleanup();
this.nftablesManager = null;
}
await this.applyNftablesRules(newRoutes);
// Update stored routes
this.settings.routes = newRoutes;
@@ -411,14 +428,59 @@ export class SmartProxy extends plugins.EventEmitter {
}
/**
* Get NFTables status (async - calls Rust).
* Get NFTables status.
*/
public async getNfTablesStatus(): Promise<Record<string, any>> {
return this.bridge.getNftablesStatus();
public getNfTablesStatus(): plugins.smartnftables.INftStatus | null {
return this.nftablesManager?.status() ?? null;
}
// --- Private helpers ---
/**
* Apply NFTables rules for routes using the nftables forwarding engine.
*/
private async applyNftablesRules(routes: IRouteConfig[]): Promise<void> {
const nftRoutes = routes.filter(r => r.action.forwardingEngine === 'nftables');
if (nftRoutes.length === 0) return;
const tableName = nftRoutes.find(r => r.action.nftables?.tableName)?.action.nftables?.tableName ?? 'smartproxy';
const nft = new plugins.smartnftables.SmartNftables({ tableName });
await nft.initialize();
for (const route of nftRoutes) {
const routeId = route.name || 'unnamed';
const targets = route.action.targets;
if (!targets) continue;
const nftOpts = route.action.nftables;
const protocol: plugins.smartnftables.TNftProtocol = (nftOpts?.protocol as any) ?? 'tcp';
const preserveSourceIP = nftOpts?.preserveSourceIP ?? false;
const ports = Array.isArray(route.match.ports)
? route.match.ports.flatMap(p => typeof p === 'number' ? [p] : [])
: typeof route.match.ports === 'number' ? [route.match.ports] : [];
for (const target of targets) {
const targetHost = Array.isArray(target.host) ? target.host[0] : target.host;
if (typeof targetHost !== 'string') continue;
for (const sourcePort of ports) {
const targetPort = typeof target.port === 'number' ? target.port : sourcePort;
await nft.nat.addPortForwarding(`${routeId}-${sourcePort}-${targetPort}`, {
sourcePort,
targetHost,
targetPort,
protocol,
preserveSourceIP,
});
}
}
}
this.nftablesManager = nft;
logger.log('info', `Applied NFTables rules for ${nftRoutes.length} route(s)`, { component: 'smart-proxy' });
}
/**
* Build the Rust configuration object from TS settings.
*/