import { NftExecutor } from './nft.executor.js'; import { buildTableSetup, buildFilterChains, buildTableCleanup } from './nft.rulebuilder.table.js'; import { NatManager } from './nft.manager.nat.js'; import { FirewallManager } from './nft.manager.firewall.js'; import { RateLimitManager } from './nft.manager.ratelimit.js'; import type { TNftFamily, INftRuleGroup, INftStatus, ISmartNftablesOptions } from './nft.types.js'; /** * SmartNftables — high-level facade for managing nftables rules. * * Provides sub-managers for NAT, firewall, and rate limiting. * All rules are tracked in logical groups and can be removed individually or cleaned up entirely. */ export class SmartNftables { public readonly nat: NatManager; public readonly firewall: FirewallManager; public readonly rateLimit: RateLimitManager; public readonly tableName: string; public readonly family: TNftFamily; public readonly executor: NftExecutor; private initialized = false; private hasFilterChains = false; private warnedNonRoot = false; private ruleGroups: Map = new Map(); constructor(options?: ISmartNftablesOptions) { this.tableName = options?.tableName ?? 'smartnftables'; this.family = options?.family ?? 'ip'; this.executor = new NftExecutor({ dryRun: options?.dryRun }); this.nat = new NatManager(this); this.firewall = new FirewallManager(this); this.rateLimit = new RateLimitManager(this); } /** * Initialize the nftables table and NAT chains. Idempotent. */ public async initialize(): Promise { if (this.initialized) return; if (!this.executor.isRoot()) { if (!this.warnedNonRoot) { console.warn('smartnftables: not running as root. Rules are tracked but not applied to kernel.'); this.warnedNonRoot = true; } this.initialized = true; return; } const commands = buildTableSetup(this.tableName, this.family); await this.executor.execBatch(commands); this.initialized = true; } /** * Ensure filter chains (input/forward/output) are created. * Called automatically when firewall or rate-limit rules are added. */ public async ensureFilterChains(): Promise { if (this.hasFilterChains) return; await this.ensureInitialized(); if (this.executor.isRoot()) { const commands = buildFilterChains(this.tableName, this.family); await this.executor.execBatch(commands); } this.hasFilterChains = true; } /** * Ensure the table is initialized before applying rules. */ public async ensureInitialized(): Promise { if (!this.initialized) { await this.initialize(); } } /** * Apply a group of nft commands and track them under the given ID. */ public async applyRuleGroup(groupId: string, commands: string[]): Promise { // Always track the group locally this.ruleGroups.set(groupId, { id: groupId, commands, createdAt: Date.now(), }); if (!this.executor.isRoot()) { if (!this.warnedNonRoot) { console.warn('smartnftables: not running as root. Rules are tracked but not applied to kernel.'); this.warnedNonRoot = true; } return; } await this.ensureInitialized(); await this.executor.execBatch(commands); } /** * Remove a tracked rule group. Removes from tracking. * Note: full kernel cleanup requires cleanup() — individual rule removal * would require handle-based tracking. */ public async removeRuleGroup(groupId: string): Promise { this.ruleGroups.delete(groupId); } /** * Get a tracked rule group by ID. */ public getRuleGroup(groupId: string): INftRuleGroup | undefined { return this.ruleGroups.get(groupId); } /** * Delete the entire nftables table and clear all tracking. */ public async cleanup(): Promise { if (this.executor.isRoot() && this.initialized) { const commands = buildTableCleanup(this.tableName, this.family); await this.executor.execBatch(commands, { continueOnError: true }); } this.ruleGroups.clear(); this.initialized = false; this.hasFilterChains = false; } /** * Get status report of the managed nftables state. */ public status(): INftStatus { const groups: Record = {}; for (const [id, group] of this.ruleGroups) { groups[id] = { ruleCount: group.commands.length, createdAt: group.createdAt, }; } return { initialized: this.initialized, tableName: this.tableName, family: this.family, isRoot: this.executor.isRoot(), activeGroups: this.ruleGroups.size, groups, }; } }