Files
smartnftables/ts/nft.manager.ts

158 lines
4.7 KiB
TypeScript
Raw Normal View History

2026-03-26 10:32:05 +00:00
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<string, INftRuleGroup> = 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<void> {
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<void> {
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<void> {
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<void> {
// 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<void> {
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<void> {
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<string, { ruleCount: number; createdAt: number }> = {};
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,
};
}
}