initial
This commit is contained in:
157
ts/nft.manager.ts
Normal file
157
ts/nft.manager.ts
Normal file
@@ -0,0 +1,157 @@
|
||||
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,
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user