@push.rocks/smartnftables 1.1.0 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/changelog.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # Changelog
2
2
 
3
+ ## 2026-07-31 - 1.2.1
4
+
5
+ ### Fixes
6
+
7
+ - normalize command execution and firewall rule generation (nft)
8
+ - Preserve quoted values and escaped semicolons when executing nft commands.
9
+ - Default port-only firewall rules to TCP and use meta l4proto for protocol-only rules.
10
+ - Escape rule comments so generated nft commands remain parseable.
11
+ - Update tests, documentation, release configuration, and package tooling dependencies.
12
+
13
+ ## 2026-04-26 - 1.2.0 - feat(firewall)
14
+ add IP set blocking convenience API with CIDR interval support and optional rule comments
15
+
16
+ - introduces firewall.blockIPSet() to create a set, populate it with IPs or CIDR ranges, and apply a single drop rule
17
+ - adds interval set support so CIDR entries can be stored in nftables sets
18
+ - supports optional comments on generated IP set match rules
19
+ - adds forced cleanup support and allows table existence checks without requiring prior initialization
20
+
3
21
  ## 2026-03-30 - 1.1.0 - feat(nft)
4
22
  add source IP filtering for DNAT rules and expose table existence checks
5
23
 
@@ -14,4 +32,4 @@ no changes to commit
14
32
  ## 2026-03-26 - 1.0.0 - project
15
33
  Initial release.
16
34
 
17
- - Project initialized with first commit.
35
+ - Project initialized with first commit.
@@ -3,7 +3,7 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@push.rocks/smartnftables',
6
- version: '1.1.0',
6
+ version: '1.2.1',
7
7
  description: 'A TypeScript module for managing nftables rules including NAT, firewall, and rate limiting with a high-level API.'
8
8
  };
9
9
  //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMDBfY29tbWl0aW5mb19kYXRhLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvMDBfY29tbWl0aW5mb19kYXRhLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOztHQUVHO0FBQ0gsTUFBTSxDQUFDLE1BQU0sVUFBVSxHQUFHO0lBQ3hCLElBQUksRUFBRSwyQkFBMkI7SUFDakMsT0FBTyxFQUFFLE9BQU87SUFDaEIsV0FBVyxFQUFFLG1IQUFtSDtDQUNqSSxDQUFBIn0=
@@ -7,4 +7,4 @@ export { buildTableSetup, buildFilterChains, buildTableCleanup, } from './nft.ru
7
7
  export { buildDnatRules, buildSnatRule, buildMasqueradeRule, } from './nft.rulebuilder.nat.js';
8
8
  export { buildRateLimitRule, buildPerIpRateLimitRule, buildConnectionRateRule, } from './nft.rulebuilder.ratelimit.js';
9
9
  export { buildFirewallRule, buildIPSetCreate, buildIPSetAddElements, buildIPSetRemoveElements, buildIPSetDelete, buildIPSetMatchRule, } from './nft.rulebuilder.firewall.js';
10
- export type { TNftProtocol, TNftFamily, TNftChainHook, TNftChainType, TNftPolicy, TFirewallAction, TCtState, INftDnatRule, INftSnatRule, INftMasqueradeRule, INftRateLimitRule, INftConnectionRateRule, INftFirewallRule, INftIPSetConfig, INftRuleGroup, ISmartNftablesOptions, INftStatus, } from './nft.types.js';
10
+ export type { TNftProtocol, TNftFamily, TNftChainHook, TNftChainType, TNftPolicy, TFirewallAction, TCtState, INftDnatRule, INftSnatRule, INftMasqueradeRule, INftRateLimitRule, INftConnectionRateRule, INftFirewallRule, INftIPSetBlockOptions, INftIPSetConfig, INftCleanupOptions, INftRuleGroup, ISmartNftablesOptions, INftStatus, } from './nft.types.js';
@@ -1,3 +1,4 @@
1
+ export declare function normalizeNftCommand(command: string): string;
1
2
  export declare class NftNotRootError extends Error {
2
3
  constructor();
3
4
  }
@@ -1,5 +1,68 @@
1
1
  import * as plugins from './plugins.js';
2
2
  const execFile = plugins.util.promisify(plugins.childProcess.execFile);
3
+ export function normalizeNftCommand(command) {
4
+ let input = command.trim();
5
+ if (!input) {
6
+ throw new Error('Invalid nft command: command is empty');
7
+ }
8
+ if (input === 'nft') {
9
+ throw new Error('Invalid nft command: command is empty');
10
+ }
11
+ if (/^nft\s/.test(input)) {
12
+ input = input.slice(3).trimStart();
13
+ }
14
+ let normalized = '';
15
+ let quote;
16
+ for (let index = 0; index < input.length; index++) {
17
+ const character = input[index];
18
+ if (quote === 'single') {
19
+ normalized += character;
20
+ if (character === "'") {
21
+ quote = undefined;
22
+ }
23
+ continue;
24
+ }
25
+ if (quote === 'double') {
26
+ normalized += character;
27
+ if (character === '"') {
28
+ quote = undefined;
29
+ continue;
30
+ }
31
+ if (character === '\\') {
32
+ const nextCharacter = input[index + 1];
33
+ if (nextCharacter === undefined) {
34
+ throw new Error('Invalid nft command: trailing escape in double-quoted string');
35
+ }
36
+ normalized += nextCharacter;
37
+ index++;
38
+ continue;
39
+ }
40
+ continue;
41
+ }
42
+ if (character === "'") {
43
+ quote = 'single';
44
+ }
45
+ else if (character === '"') {
46
+ quote = 'double';
47
+ }
48
+ else if (character === '\\') {
49
+ const nextCharacter = input[index + 1];
50
+ if (nextCharacter === undefined) {
51
+ throw new Error('Invalid nft command: trailing escape');
52
+ }
53
+ if (nextCharacter === ';') {
54
+ normalized += ';';
55
+ index++;
56
+ continue;
57
+ }
58
+ }
59
+ normalized += character;
60
+ }
61
+ if (quote) {
62
+ throw new Error(`Invalid nft command: unterminated ${quote}-quoted string`);
63
+ }
64
+ return normalized;
65
+ }
3
66
  export class NftNotRootError extends Error {
4
67
  constructor() {
5
68
  super('Not running as root — nftables commands require root privileges');
@@ -31,12 +94,11 @@ export class NftExecutor {
31
94
  * Returns stdout on success.
32
95
  */
33
96
  async exec(command) {
97
+ const normalizedCommand = normalizeNftCommand(command);
34
98
  if (this.dryRun) {
35
99
  return '';
36
100
  }
37
- // Strip "nft " prefix if present
38
- const args = command.startsWith('nft ') ? command.slice(4) : command;
39
- const { stdout } = await execFile('nft', args.split(/\s+/));
101
+ const { stdout } = await execFile('nft', [normalizedCommand]);
40
102
  return stdout;
41
103
  }
42
104
  /**
@@ -56,4 +118,4 @@ export class NftExecutor {
56
118
  }
57
119
  }
58
120
  }
59
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibmZ0LmV4ZWN1dG9yLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvbmZ0LmV4ZWN1dG9yLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxPQUFPLE1BQU0sY0FBYyxDQUFDO0FBRXhDLE1BQU0sUUFBUSxHQUFHLE9BQU8sQ0FBQyxJQUFJLENBQUMsU0FBUyxDQUFDLE9BQU8sQ0FBQyxZQUFZLENBQUMsUUFBUSxDQUFDLENBQUM7QUFFdkUsTUFBTSxPQUFPLGVBQWdCLFNBQVEsS0FBSztJQUN4QztRQUNFLEtBQUssQ0FBQyxpRUFBaUUsQ0FBQyxDQUFDO1FBQ3pFLElBQUksQ0FBQyxJQUFJLEdBQUcsaUJBQWlCLENBQUM7SUFDaEMsQ0FBQztDQUNGO0FBRUQsTUFBTSxPQUFPLGVBQWdCLFNBQVEsS0FBSztJQUl4QyxZQUFZLE9BQWUsRUFBRSxNQUFjO1FBQ3pDLEtBQUssQ0FBQyx1QkFBdUIsT0FBTyxNQUFNLE1BQU0sRUFBRSxDQUFDLENBQUM7UUFDcEQsSUFBSSxDQUFDLElBQUksR0FBRyxpQkFBaUIsQ0FBQztRQUM5QixJQUFJLENBQUMsT0FBTyxHQUFHLE9BQU8sQ0FBQztRQUN2QixJQUFJLENBQUMsTUFBTSxHQUFHLE1BQU0sQ0FBQztJQUN2QixDQUFDO0NBQ0Y7QUFFRDs7R0FFRztBQUNILE1BQU0sT0FBTyxXQUFXO0lBR3RCLFlBQVksT0FBOEI7UUFDeEMsSUFBSSxDQUFDLE1BQU0sR0FBRyxPQUFPLEVBQUUsTUFBTSxJQUFJLEtBQUssQ0FBQztJQUN6QyxDQUFDO0lBRUQseUNBQXlDO0lBQ2xDLE1BQU07UUFDWCxPQUFPLE9BQU8sQ0FBQyxNQUFNLEVBQUUsRUFBRSxLQUFLLENBQUMsQ0FBQztJQUNsQyxDQUFDO0lBRUQ7Ozs7T0FJRztJQUNJLEtBQUssQ0FBQyxJQUFJLENBQUMsT0FBZTtRQUMvQixJQUFJLElBQUksQ0FBQyxNQUFNLEVBQUUsQ0FBQztZQUNoQixPQUFPLEVBQUUsQ0FBQztRQUNaLENBQUM7UUFFRCxpQ0FBaUM7UUFDakMsTUFBTSxJQUFJLEdBQUcsT0FBTyxDQUFDLFVBQVUsQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDLENBQUMsT0FBTyxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsT0FBTyxDQUFDO1FBRXJFLE1BQU0sRUFBRSxNQUFNLEVBQUUsR0FBRyxNQUFNLFFBQVEsQ0FBQyxLQUFLLEVBQUUsSUFBSSxDQUFDLEtBQUssQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDO1FBQzVELE9BQU8sTUFBTSxDQUFDO0lBQ2hCLENBQUM7SUFFRDs7O09BR0c7SUFDSSxLQUFLLENBQUMsU0FBUyxDQUNwQixRQUFrQixFQUNsQixPQUF1QztRQUV2QyxLQUFLLE1BQU0sR0FBRyxJQUFJLFFBQVEsRUFBRSxDQUFDO1lBQzNCLElBQUksQ0FBQztnQkFDSCxNQUFNLElBQUksQ0FBQyxJQUFJLENBQUMsR0FBRyxDQUFDLENBQUM7WUFDdkIsQ0FBQztZQUFDLE9BQU8sR0FBRyxFQUFFLENBQUM7Z0JBQ2IsSUFBSSxDQUFDLE9BQU8sRUFBRSxlQUFlLEVBQUUsQ0FBQztvQkFDOUIsTUFBTSxHQUFHLENBQUM7Z0JBQ1osQ0FBQztZQUNILENBQUM7UUFDSCxDQUFDO0lBQ0gsQ0FBQztDQUNGIn0=
121
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibmZ0LmV4ZWN1dG9yLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvbmZ0LmV4ZWN1dG9yLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxPQUFPLE1BQU0sY0FBYyxDQUFDO0FBRXhDLE1BQU0sUUFBUSxHQUFHLE9BQU8sQ0FBQyxJQUFJLENBQUMsU0FBUyxDQUFDLE9BQU8sQ0FBQyxZQUFZLENBQUMsUUFBUSxDQUFDLENBQUM7QUFFdkUsTUFBTSxVQUFVLG1CQUFtQixDQUFDLE9BQWU7SUFDakQsSUFBSSxLQUFLLEdBQUcsT0FBTyxDQUFDLElBQUksRUFBRSxDQUFDO0lBQzNCLElBQUksQ0FBQyxLQUFLLEVBQUUsQ0FBQztRQUNYLE1BQU0sSUFBSSxLQUFLLENBQUMsdUNBQXVDLENBQUMsQ0FBQztJQUMzRCxDQUFDO0lBQ0QsSUFBSSxLQUFLLEtBQUssS0FBSyxFQUFFLENBQUM7UUFDcEIsTUFBTSxJQUFJLEtBQUssQ0FBQyx1Q0FBdUMsQ0FBQyxDQUFDO0lBQzNELENBQUM7SUFDRCxJQUFJLFFBQVEsQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLEVBQUUsQ0FBQztRQUN6QixLQUFLLEdBQUcsS0FBSyxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsQ0FBQyxTQUFTLEVBQUUsQ0FBQztJQUNyQyxDQUFDO0lBRUQsSUFBSSxVQUFVLEdBQUcsRUFBRSxDQUFDO0lBQ3BCLElBQUksS0FBc0MsQ0FBQztJQUUzQyxLQUFLLElBQUksS0FBSyxHQUFHLENBQUMsRUFBRSxLQUFLLEdBQUcsS0FBSyxDQUFDLE1BQU0sRUFBRSxLQUFLLEVBQUUsRUFBRSxDQUFDO1FBQ2xELE1BQU0sU0FBUyxHQUFHLEtBQUssQ0FBQyxLQUFLLENBQUMsQ0FBQztRQUUvQixJQUFJLEtBQUssS0FBSyxRQUFRLEVBQUUsQ0FBQztZQUN2QixVQUFVLElBQUksU0FBUyxDQUFDO1lBQ3hCLElBQUksU0FBUyxLQUFLLEdBQUcsRUFBRSxDQUFDO2dCQUN0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
@@ -2,7 +2,7 @@ import { NftExecutor } from './nft.executor.js';
2
2
  import { NatManager } from './nft.manager.nat.js';
3
3
  import { FirewallManager } from './nft.manager.firewall.js';
4
4
  import { RateLimitManager } from './nft.manager.ratelimit.js';
5
- import type { TNftFamily, INftRuleGroup, INftStatus, ISmartNftablesOptions } from './nft.types.js';
5
+ import type { TNftFamily, INftCleanupOptions, INftRuleGroup, INftStatus, ISmartNftablesOptions } from './nft.types.js';
6
6
  /**
7
7
  * SmartNftables — high-level facade for managing nftables rules.
8
8
  *
@@ -51,7 +51,7 @@ export declare class SmartNftables {
51
51
  /**
52
52
  * Delete the entire nftables table and clear all tracking.
53
53
  */
54
- cleanup(): Promise<void>;
54
+ cleanup(options?: INftCleanupOptions): Promise<void>;
55
55
  /**
56
56
  * Check if the managed nftables table exists in the kernel.
57
57
  * Returns false if not root, not initialized, or the table was removed externally.
@@ -1,5 +1,5 @@
1
1
  import type { SmartNftables } from './nft.manager.js';
2
- import type { INftFirewallRule, INftIPSetConfig } from './nft.types.js';
2
+ import type { INftFirewallRule, INftIPSetBlockOptions, INftIPSetConfig } from './nft.types.js';
3
3
  /**
4
4
  * Manages firewall (filter) rules, IP sets, and convenience methods.
5
5
  */
@@ -36,6 +36,11 @@ export declare class FirewallManager {
36
36
  blockIP(ip: string, options?: {
37
37
  direction?: 'input' | 'forward';
38
38
  }): Promise<void>;
39
+ /**
40
+ * Convenience: block many IPs or CIDR subnets using one nftables set and
41
+ * one matching drop rule. This is substantially cheaper than one rule per IP.
42
+ */
43
+ blockIPSet(groupId: string, options: INftIPSetBlockOptions): Promise<void>;
39
44
  /**
40
45
  * Convenience: allow only specific IPs on a port.
41
46
  * Adds accept rules for each IP, then a drop rule for everything else on that port.
@@ -66,6 +66,37 @@ export class FirewallManager {
66
66
  sourceIP: ip,
67
67
  });
68
68
  }
69
+ /**
70
+ * Convenience: block many IPs or CIDR subnets using one nftables set and
71
+ * one matching drop rule. This is substantially cheaper than one rule per IP.
72
+ */
73
+ async blockIPSet(groupId, options) {
74
+ const ips = [...new Set(options.ips)].filter(Boolean).sort();
75
+ if (ips.length === 0) {
76
+ return;
77
+ }
78
+ await this.parent.ensureFilterChains();
79
+ const setName = options.setName ?? `${groupId.replace(/[^a-zA-Z0-9_]/g, '_')}_set`;
80
+ const setType = options.type ?? (this.parent.family === 'ip6' ? 'ipv6_addr' : 'ipv4_addr');
81
+ const needsIntervalSet = ips.some((ip) => ip.includes('/'));
82
+ const direction = options.direction ?? 'input';
83
+ const commands = [
84
+ ...buildIPSetCreate(this.parent.tableName, this.parent.family, {
85
+ name: setName,
86
+ type: setType,
87
+ elements: ips,
88
+ interval: needsIntervalSet,
89
+ }),
90
+ ...buildIPSetMatchRule(this.parent.tableName, this.parent.family, {
91
+ setName,
92
+ direction,
93
+ matchField: 'saddr',
94
+ action: 'drop',
95
+ comment: options.comment,
96
+ }),
97
+ ];
98
+ await this.parent.applyRuleGroup(`fw:blockset:${groupId}`, commands);
99
+ }
69
100
  /**
70
101
  * Convenience: allow only specific IPs on a port.
71
102
  * Adds accept rules for each IP, then a drop rule for everything else on that port.
@@ -118,4 +149,4 @@ export class FirewallManager {
118
149
  await this.parent.applyRuleGroup(`fw:stateful:${direction}`, commands);
119
150
  }
120
151
  }
121
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibmZ0Lm1hbmFnZXIuZmlyZXdhbGwuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9uZnQubWFuYWdlci5maXJld2FsbC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFFQSxPQUFPLEVBQ0wsaUJBQWlCLEVBQ2pCLGdCQUFnQixFQUNoQixxQkFBcUIsRUFDckIsd0JBQXdCLEVBQ3hCLGdCQUFnQixFQUNoQixtQkFBbUIsR0FDcEIsTUFBTSwrQkFBK0IsQ0FBQztBQUV2Qzs7R0FFRztBQUNILE1BQU0sT0FBTyxlQUFlO0lBQzFCLFlBQW9CLE1BQXFCO1FBQXJCLFdBQU0sR0FBTixNQUFNLENBQWU7SUFBRyxDQUFDO0lBRTdDOztPQUVHO0lBQ0ksS0FBSyxDQUFDLE9BQU8sQ0FBQyxPQUFlLEVBQUUsSUFBc0I7UUFDMUQsTUFBTSxJQUFJLENBQUMsTUFBTSxDQUFDLGtCQUFrQixFQUFFLENBQUM7UUFDdkMsTUFBTSxRQUFRLEdBQUcsaUJBQWlCLENBQUMsSUFBSSxDQUFDLE1BQU0sQ0FBQyxTQUFTLEVBQUUsSUFBSSxDQUFDLE1BQU0sQ0FBQyxNQUFNLEVBQUUsSUFBSSxDQUFDLENBQUM7UUFDcEYsTUFBTSxJQUFJLENBQUMsTUFBTSxDQUFDLGNBQWMsQ0FBQyxNQUFNLE9BQU8sRUFBRSxFQUFFLFFBQVEsQ0FBQyxDQUFDO0lBQzlELENBQUM7SUFFRDs7T0FFRztJQUNJLEtBQUssQ0FBQyxVQUFVLENBQUMsT0FBZTtRQUNyQyxNQUFNLElBQUksQ0FBQyxNQUFNLENBQUMsZUFBZSxDQUFDLE1BQU0sT0FBTyxFQUFFLENBQUMsQ0FBQztJQUNyRCxDQUFDO0lBRUQ7O09BRUc7SUFDSSxLQUFLLENBQUMsV0FBVyxDQUFDLE1BQXVCO1FBQzlDLE1BQU0sSUFBSSxDQUFDLE1BQU0sQ0FBQyxpQkFBaUIsRUFBRSxDQUFDO1FBQ3RDLE1BQU0sUUFBUSxHQUFHLGdCQUFnQixDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsU0FBUyxFQUFFLElBQUksQ0FB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
152
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibmZ0Lm1hbmFnZXIuZmlyZXdhbGwuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9uZnQubWFuYWdlci5maXJld2FsbC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFFQSxPQUFPLEVBQ0wsaUJBQWlCLEVBQ2pCLGdCQUFnQixFQUNoQixxQkFBcUIsRUFDckIsd0JBQXdCLEVBQ3hCLGdCQUFnQixFQUNoQixtQkFBbUIsR0FDcEIsTUFBTSwrQkFBK0IsQ0FBQztBQUV2Qzs7R0FFRztBQUNILE1BQU0sT0FBTyxlQUFlO0lBQzFCLFlBQW9CLE1BQXFCO1FBQXJCLFdBQU0sR0FBTixNQUFNLENBQWU7SUFBRyxDQUFDO0lBRTdDOztPQUVHO0lBQ0ksS0FBSyxDQUFDLE9BQU8sQ0FBQyxPQUFlLEVBQUUsSUFBc0I7UUFDMUQsTUFBTSxJQUFJLENBQUMsTUFBTSxDQUFDLGtCQUFrQixFQUFFLENBQUM7UUFDdkMsTUFBTSxRQUFRLEdBQUcsaUJBQWlCLENBQUMsSUFBSSxDQUFDLE1BQU0sQ0FBQyxTQUFTLEVBQUUsSUFBSSxDQUFDLE1BQU0sQ0FBQyxNQUFNLEVBQUUsSUFBSSxDQUFDLENBQUM7UUFDcEYsTUFBTSxJQUFJLENBQUMsTUFBTSxDQUFDLGNBQWMsQ0FBQyxNQUFNLE9BQU8sRUFBRSxFQUFFLFFBQVEsQ0FBQyxDQUFDO0lBQzlELENBQUM7SUFFRDs7T0FFRztJQUNJLEtBQUssQ0FBQyxVQUFVLENBQUMsT0FBZTtRQUNyQyxNQUFNLElBQUksQ0FBQyxNQUFNLENBQUMsZUFBZSxDQUFDLE1BQU0sT0FBTyxFQUFFLENBQUMsQ0FBQztJQUNyRCxDQUFDO0lBRUQ7O09BRUc7SUFDSSxLQUFLLENBQUMsV0FBVyxDQUFDLE1BQXVCO1FBQzlDLE1BQU0sSUFBSSxDQUFDLE1BQU0sQ0FBQyxpQkFBaUIsRUFBRSxDQUFDO1FBQ3RDLE1BQU0sUUFBUSxHQUFHLGdCQUFnQixDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsU0FBUyxFQUFFLElBQUksQ0FB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
@@ -99,8 +99,8 @@ export class SmartNftables {
99
99
  /**
100
100
  * Delete the entire nftables table and clear all tracking.
101
101
  */
102
- async cleanup() {
103
- if (this.executor.isRoot() && this.initialized) {
102
+ async cleanup(options = {}) {
103
+ if (this.executor.isRoot() && (this.initialized || options.force)) {
104
104
  const commands = buildTableCleanup(this.tableName, this.family);
105
105
  await this.executor.execBatch(commands, { continueOnError: true });
106
106
  }
@@ -113,7 +113,7 @@ export class SmartNftables {
113
113
  * Returns false if not root, not initialized, or the table was removed externally.
114
114
  */
115
115
  async tableExists() {
116
- if (!this.executor.isRoot() || !this.initialized) {
116
+ if (!this.executor.isRoot()) {
117
117
  return false;
118
118
  }
119
119
  try {
@@ -145,4 +145,4 @@ export class SmartNftables {
145
145
  };
146
146
  }
147
147
  }
148
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibmZ0Lm1hbmFnZXIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9uZnQubWFuYWdlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsV0FBVyxFQUFFLE1BQU0sbUJBQW1CLENBQUM7QUFDaEQsT0FBTyxFQUFFLGVBQWUsRUFBRSxpQkFBaUIsRUFBRSxpQkFBaUIsRUFBRSxNQUFNLDRCQUE0QixDQUFDO0FBQ25HLE9BQU8sRUFBRSxVQUFVLEVBQUUsTUFBTSxzQkFBc0IsQ0FBQztBQUNsRCxPQUFPLEVBQUUsZUFBZSxFQUFFLE1BQU0sMkJBQTJCLENBQUM7QUFDNUQsT0FBTyxFQUFFLGdCQUFnQixFQUFFLE1BQU0sNEJBQTRCLENBQUM7QUFHOUQ7Ozs7O0dBS0c7QUFDSCxNQUFNLE9BQU8sYUFBYTtJQWN4QixZQUFZLE9BQStCO1FBTG5DLGdCQUFXLEdBQUcsS0FBSyxDQUFDO1FBQ3BCLG9CQUFlLEdBQUcsS0FBSyxDQUFDO1FBQ3hCLGtCQUFhLEdBQUcsS0FBSyxDQUFDO1FBQ3RCLGVBQVUsR0FBK0IsSUFBSSxHQUFHLEVBQUUsQ0FBQztRQUd6RCxJQUFJLENBQUMsU0FBUyxHQUFHLE9BQU8sRUFBRSxTQUFTLElBQUksZUFBZSxDQUFDO1FBQ3ZELElBQUksQ0FBQyxNQUFNLEdBQUcsT0FBTyxFQUFFLE1BQU0sSUFBSSxJQUFJLENBQUM7UUFDdEMsSUFBSSxDQUFDLFFBQVEsR0FBRyxJQUFJLFdBQVcsQ0FBQyxFQUFFLE1BQU0sRUFBRSxPQUFPLEVBQUUsTUFBTSxFQUFFLENBQUMsQ0FBQztRQUU3RCxJQUFJLENBQUMsR0FBRyxHQUFHLElBQUksVUFBVSxDQUFDLElBQUksQ0FBQyxDQUFDO1FBQ2hDLElBQUksQ0FBQyxRQUFRLEdBQUcsSUFBSSxlQUFlLENBQUMsSUFBSSxDQUFDLENBQUM7UUFDMUMsSUFBSSxDQUFDLFNBQVMsR0FBRyxJQUFJLGdCQUFnQixDQUFDLElBQUksQ0FBQyxDQUFDO0lBQzlDLENBQUM7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
148
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibmZ0Lm1hbmFnZXIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9uZnQubWFuYWdlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsV0FBVyxFQUFFLE1BQU0sbUJBQW1CLENBQUM7QUFDaEQsT0FBTyxFQUFFLGVBQWUsRUFBRSxpQkFBaUIsRUFBRSxpQkFBaUIsRUFBRSxNQUFNLDRCQUE0QixDQUFDO0FBQ25HLE9BQU8sRUFBRSxVQUFVLEVBQUUsTUFBTSxzQkFBc0IsQ0FBQztBQUNsRCxPQUFPLEVBQUUsZUFBZSxFQUFFLE1BQU0sMkJBQTJCLENBQUM7QUFDNUQsT0FBTyxFQUFFLGdCQUFnQixFQUFFLE1BQU0sNEJBQTRCLENBQUM7QUFHOUQ7Ozs7O0dBS0c7QUFDSCxNQUFNLE9BQU8sYUFBYTtJQWN4QixZQUFZLE9BQStCO1FBTG5DLGdCQUFXLEdBQUcsS0FBSyxDQUFDO1FBQ3BCLG9CQUFlLEdBQUcsS0FBSyxDQUFDO1FBQ3hCLGtCQUFhLEdBQUcsS0FBSyxDQUFDO1FBQ3RCLGVBQVUsR0FBK0IsSUFBSSxHQUFHLEVBQUUsQ0FBQztRQUd6RCxJQUFJLENBQUMsU0FBUyxHQUFHLE9BQU8sRUFBRSxTQUFTLElBQUksZUFBZSxDQUFDO1FBQ3ZELElBQUksQ0FBQyxNQUFNLEdBQUcsT0FBTyxFQUFFLE1BQU0sSUFBSSxJQUFJLENBQUM7UUFDdEMsSUFBSSxDQUFDLFFBQVEsR0FBRyxJQUFJLFdBQVcsQ0FBQyxFQUFFLE1BQU0sRUFBRSxPQUFPLEVBQUUsTUFBTSxFQUFFLENBQUMsQ0FBQztRQUU3RCxJQUFJLENBQUMsR0FBRyxHQUFHLElBQUksVUFBVSxDQUFDLElBQUksQ0FBQyxDQUFDO1FBQ2hDLElBQUksQ0FBQyxRQUFRLEdBQUcsSUFBSSxlQUFlLENBQUMsSUFBSSxDQUFDLENBQUM7UUFDMUMsSUFBSSxDQUFDLFNBQVMsR0FBRyxJQUFJLGdCQUFnQixDQUFDLElBQUksQ0FBQyxDQUFDO0lBQzlDLENBQUM7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
@@ -27,4 +27,5 @@ export declare function buildIPSetMatchRule(tableName: string, family: TNftFamil
27
27
  direction: 'input' | 'output' | 'forward';
28
28
  matchField: 'saddr' | 'daddr';
29
29
  action: 'accept' | 'drop' | 'reject';
30
+ comment?: string;
30
31
  }): string[];
@@ -1,3 +1,6 @@
1
+ function escapeNftString(value) {
2
+ return value.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
3
+ }
1
4
  /**
2
5
  * Build an nft firewall rule for input/output/forward chains.
3
6
  */
@@ -9,12 +12,15 @@ export function buildFirewallRule(tableName, family, rule) {
9
12
  parts.push(`ct state { ${rule.ctStates.join(', ')} }`);
10
13
  }
11
14
  // Protocol and port matching
12
- const protocols = expandProtocols(rule.protocol);
15
+ const hasPort = rule.sourcePort != null || rule.destPort != null;
16
+ const protocols = expandProtocols(rule.protocol ?? (hasPort ? 'tcp' : undefined));
17
+ const protocolVariants = protocols.length > 0 ? protocols : [undefined];
13
18
  const commands = [];
14
- for (const proto of protocols) {
19
+ for (const proto of protocolVariants) {
15
20
  const ruleParts = [...parts];
16
- if (rule.protocol) {
17
- ruleParts.push(proto);
21
+ const portProtocol = proto ?? 'tcp';
22
+ if (proto && !hasPort) {
23
+ ruleParts.push(`meta l4proto ${proto}`);
18
24
  }
19
25
  if (rule.sourceIP) {
20
26
  ruleParts.push(`ip saddr ${rule.sourceIP}`);
@@ -23,36 +29,15 @@ export function buildFirewallRule(tableName, family, rule) {
23
29
  ruleParts.push(`ip daddr ${rule.destIP}`);
24
30
  }
25
31
  if (rule.sourcePort != null) {
26
- ruleParts.push(`${proto} sport ${rule.sourcePort}`);
32
+ ruleParts.push(`${portProtocol} sport ${rule.sourcePort}`);
27
33
  }
28
34
  if (rule.destPort != null) {
29
- // If protocol wasn't explicitly set but we have a port, we need the protocol
30
- if (!rule.protocol) {
31
- ruleParts.push(`tcp dport ${rule.destPort}`);
32
- }
33
- else {
34
- ruleParts.push(`${proto} dport ${rule.destPort}`);
35
- }
36
- }
37
- if (rule.comment) {
38
- ruleParts.push(`comment "${rule.comment}"`);
35
+ ruleParts.push(`${portProtocol} dport ${rule.destPort}`);
39
36
  }
40
37
  ruleParts.push(rule.action);
41
- commands.push(ruleParts.join(' '));
42
- }
43
- // If no protocol expansion needed (no protocol-specific fields)
44
- if (commands.length === 0) {
45
- const ruleParts = [...parts];
46
- if (rule.sourceIP) {
47
- ruleParts.push(`ip saddr ${rule.sourceIP}`);
48
- }
49
- if (rule.destIP) {
50
- ruleParts.push(`ip daddr ${rule.destIP}`);
51
- }
52
38
  if (rule.comment) {
53
- ruleParts.push(`comment "${rule.comment}"`);
39
+ ruleParts.push(`comment "${escapeNftString(rule.comment)}"`);
54
40
  }
55
- ruleParts.push(rule.action);
56
41
  commands.push(ruleParts.join(' '));
57
42
  }
58
43
  return commands;
@@ -62,8 +47,9 @@ export function buildFirewallRule(tableName, family, rule) {
62
47
  */
63
48
  export function buildIPSetCreate(tableName, family, config) {
64
49
  const commands = [];
50
+ const flags = config.interval ? ' flags interval \\;' : '';
65
51
  // Create the set
66
- commands.push(`nft add set ${family} ${tableName} ${config.name} { type ${config.type} \\; }`);
52
+ commands.push(`nft add set ${family} ${tableName} ${config.name} { type ${config.type} \\;${flags} }`);
67
53
  // Add initial elements if provided
68
54
  if (config.elements && config.elements.length > 0) {
69
55
  commands.push(`nft add element ${family} ${tableName} ${config.name} { ${config.elements.join(', ')} }`);
@@ -102,8 +88,9 @@ export function buildIPSetDelete(tableName, family, setName) {
102
88
  * Build a rule that matches against a named set.
103
89
  */
104
90
  export function buildIPSetMatchRule(tableName, family, options) {
91
+ const comment = options.comment ? ` comment "${escapeNftString(options.comment)}"` : '';
105
92
  return [
106
- `nft add rule ${family} ${tableName} ${options.direction} ip ${options.matchField} @${options.setName} ${options.action}`
93
+ `nft add rule ${family} ${tableName} ${options.direction} ip ${options.matchField} @${options.setName} ${options.action}${comment}`
107
94
  ];
108
95
  }
109
96
  // ─── Internal helpers ─────────────────────────────────────────────
@@ -116,4 +103,4 @@ function expandProtocols(protocol) {
116
103
  case 'both': return ['tcp', 'udp'];
117
104
  }
118
105
  }
119
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibmZ0LnJ1bGVidWlsZGVyLmZpcmV3YWxsLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvbmZ0LnJ1bGVidWlsZGVyLmZpcmV3YWxsLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUVBOztHQUVHO0FBQ0gsTUFBTSxVQUFVLGlCQUFpQixDQUMvQixTQUFpQixFQUNqQixNQUFrQixFQUNsQixJQUFzQjtJQUV0QixNQUFNLEtBQUssR0FBRyxJQUFJLENBQUMsU0FBUyxDQUFDO0lBQzdCLE1BQU0sS0FBSyxHQUFhLENBQUMsZ0JBQWdCLE1BQU0sSUFBSSxTQUFTLElBQUksS0FBSyxFQUFFLENBQUMsQ0FBQztJQUV6RSw2QkFBNkI7SUFDN0IsSUFBSSxJQUFJLENBQUMsUUFBUSxJQUFJLElBQUksQ0FBQyxRQUFRLENBQUMsTUFBTSxHQUFHLENBQUMsRUFBRSxDQUFDO1FBQzlDLEtBQUssQ0FBQyxJQUFJLENBQUMsY0FBYyxJQUFJLENBQUMsUUFBUSxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLENBQUM7SUFDekQsQ0FBQztJQUVELDZCQUE2QjtJQUM3QixNQUFNLFNBQVMsR0FBRyxlQUFlLENBQUMsSUFBSSxDQUFDLFFBQVEsQ0FBQyxDQUFDO0lBQ2pELE1BQU0sUUFBUSxHQUFhLEVBQUUsQ0FBQztJQUU5QixLQUFLLE1BQU0sS0FBSyxJQUFJLFNBQVMsRUFBRSxDQUFDO1FBQzlCLE1BQU0sU0FBUyxHQUFHLENBQUMsR0FBRyxLQUFLLENBQUMsQ0FBQztRQUU3QixJQUFJLElBQUksQ0FBQyxRQUFRLEVBQUUsQ0FBQztZQUNsQixTQUFTLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxDQUFDO1FBQ3hCLENBQUM7UUFFRCxJQUFJLElBQUksQ0FBQyxRQUFRLEVBQUUsQ0FBQztZQUNsQixTQUFTLENBQUMsSUFBSSxDQUFDLFlBQVksSUFBSSxDQUFDLFFBQVEsRUFBRSxDQUFDLENBQUM7UUFDOUMsQ0FBQztRQUVELElBQUksSUFB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
106
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibmZ0LnJ1bGVidWlsZGVyLmZpcmV3YWxsLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvbmZ0LnJ1bGVidWlsZGVyLmZpcmV3YWxsLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUVBLFNBQVMsZUFBZSxDQUFDLEtBQWE7SUFDcEMsT0FBTyxLQUFLLENBQUMsT0FBTyxDQUFDLEtBQUssRUFBRSxNQUFNLENBQUMsQ0FBQyxPQUFPLENBQUMsSUFBSSxFQUFFLEtBQUssQ0FBQyxDQUFDO0FBQzNELENBQUM7QUFFRDs7R0FFRztBQUNILE1BQU0sVUFBVSxpQkFBaUIsQ0FDL0IsU0FBaUIsRUFDakIsTUFBa0IsRUFDbEIsSUFBc0I7SUFFdEIsTUFBTSxLQUFLLEdBQUcsSUFBSSxDQUFDLFNBQVMsQ0FBQztJQUM3QixNQUFNLEtBQUssR0FBYSxDQUFDLGdCQUFnQixNQUFNLElBQUksU0FBUyxJQUFJLEtBQUssRUFBRSxDQUFDLENBQUM7SUFFekUsNkJBQTZCO0lBQzdCLElBQUksSUFBSSxDQUFDLFFBQVEsSUFBSSxJQUFJLENBQUMsUUFBUSxDQUFDLE1BQU0sR0FBRyxDQUFDLEVBQUUsQ0FBQztRQUM5QyxLQUFLLENBQUMsSUFBSSxDQUFDLGNBQWMsSUFBSSxDQUFDLFFBQVEsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxDQUFDO0lBQ3pELENBQUM7SUFFRCw2QkFBNkI7SUFDN0IsTUFBTSxPQUFPLEdBQUcsSUFBSSxDQUFDLFVBQVUsSUFBSSxJQUFJLElBQUksSUFBSSxDQUFDLFFBQVEsSUFBSSxJQUFJLENBQUM7SUFDakUsTUFBTSxTQUFTLEdBQUcsZUFBZSxDQUFDLElBQUksQ0FBQyxRQUFRLElBQUksQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsU0FBUyxDQUFDLENBQUMsQ0FBQztJQUNsRixNQUFNLGdCQUFnQixHQUFHLFNBQVMsQ0FBQyxNQUFNLEdBQUcsQ0FBQyxDQUFDLENBQUMs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QUksT0FBTyxNQUFNLFFBQVEsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLElBQUk7S0FDL0UsQ0FBQztBQUNKLENBQUM7QUFFRDs7R0FFRztBQUNILE1BQU0sVUFBVSx3QkFBd0IsQ0FDdEMsU0FBaUIsRUFDakIsTUFBa0IsRUFDbEIsT0FBZSxFQUNmLFFBQWtCO0lBRWxCLElBQUksUUFBUSxDQUFDLE1BQU0sS0FBSyxDQUFDO1FBQUUsT0FBTyxFQUFFLENBQUM7SUFDckMsT0FBTztRQUNMLHNCQUFzQixNQUFNLElBQUksU0FBUyxJQUFJLE9BQU8sTUFBTSxRQUFRLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxJQUFJO0tBQ2xGLENBQUM7QUFDSixDQUFDO0FBRUQ7O0dBRUc7QUFDSCxNQUFNLFVBQVUsZ0JBQWdCLENBQzlCLFNBQWlCLEVBQ2pCLE1BQWtCLEVBQ2xCLE9BQWU7SUFFZixPQUFPO1FBQ0wsa0JBQWtCLE1BQU0sSUFBSSxTQUFTLElBQUksT0FBTyxFQUFFO0tBQ25ELENBQUM7QUFDSixDQUFDO0FBRUQ7O0dBRUc7QUFDSCxNQUFNLFVBQVUsbUJBQW1CLENBQ2pDLFNBQWlCLEVBQ2pCLE1BQWtCLEVBQ2xCLE9BTUM7SUFFRCxNQUFNLE9BQU8sR0FBRyxPQUFPLENBQUMsT0FBTyxDQUFDLENBQUMsQ0FBQyxhQUFhLGVBQWUsQ0FBQyxPQUFPLENBQUMsT0FBTyxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDO0lBQ3hGLE9BQU87UUFDTCxnQkFBZ0IsTUFBTSxJQUFJLFNBQVMsSUFBSSxPQUFPLENBQUMsU0FBUyxPQUFPLE9BQU8sQ0FBQyxVQUFVLEtBQUssT0FBTyxDQUFDLE9BQU8sSUFBSSxPQUFPLENBQUMsTUFBTSxHQUFHLE9BQU8sRUFBRTtLQUNwSSxDQUFDO0FBQ0osQ0FBQztBQUVELHFFQUFxRTtBQUVyRSxTQUFTLGVBQWUsQ0FBQyxRQUFpQztJQUN4RCxJQUFJLENBQUMsUUFBUTtRQUFFLE9BQU8sRUFBRSxDQUFDO0lBQ3pCLFFBQVEsUUFBUSxFQUFFLENBQUM7UUFDakIsS0FBSyxLQUFLLENBQUMsQ0FBQyxPQUFPLENBQUMsS0FBSyxDQUFDLENBQUM7UUFDM0IsS0FBSyxLQUFLLENBQUMsQ0FBQyxPQUFPLENBQUMsS0FBSyxDQUFDLENBQUM7UUFDM0IsS0FBSyxNQUFNLENBQUMsQ0FBQyxPQUFPLENBQUMsS0FBSyxFQUFFLEtBQUssQ0FBQyxDQUFDO0lBQ3JDLENBQUM7QUFDSCxDQUFDIn0=
@@ -62,8 +62,26 @@ export interface INftIPSetConfig {
62
62
  name: string;
63
63
  type: 'ipv4_addr' | 'ipv6_addr' | 'inet_service';
64
64
  elements?: string[];
65
+ /** Enable interval sets so CIDR/range elements can be stored. */
66
+ interval?: boolean;
65
67
  comment?: string;
66
68
  }
69
+ export interface INftIPSetBlockOptions {
70
+ /** Name of the nftables set to create and match against. */
71
+ setName?: string;
72
+ /** IPs or CIDR ranges to add to the set. */
73
+ ips: string[];
74
+ /** Chain to apply the block rule to. Default: input. */
75
+ direction?: 'input' | 'forward';
76
+ /** Set type. Defaults to ipv4_addr for the default ip family. */
77
+ type?: 'ipv4_addr' | 'ipv6_addr';
78
+ /** Optional rule comment. */
79
+ comment?: string;
80
+ }
81
+ export interface INftCleanupOptions {
82
+ /** Delete the table even when this process did not initialize it. */
83
+ force?: boolean;
84
+ }
67
85
  export interface INftRuleGroup {
68
86
  id: string;
69
87
  commands: string[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@push.rocks/smartnftables",
3
- "version": "1.1.0",
3
+ "version": "1.2.1",
4
4
  "private": false,
5
5
  "description": "A TypeScript module for managing nftables rules including NAT, firewall, and rate limiting with a high-level API.",
6
6
  "main": "dist_ts/index.js",
@@ -8,22 +8,17 @@
8
8
  "type": "module",
9
9
  "author": "Lossless GmbH",
10
10
  "license": "MIT",
11
- "scripts": {
12
- "test": "(tstest test/**/test*.ts --verbose --timeout 60)",
13
- "build": "(tsbuild tsfolders)",
14
- "format": "(gitzone format)",
15
- "buildDocs": "tsdoc"
16
- },
17
11
  "devDependencies": {
18
- "@git.zone/tsbuild": "^4.4.0",
19
- "@git.zone/tsrun": "^2.0.2",
20
- "@git.zone/tstest": "^3.6.0",
21
- "@types/node": "^25.5.0",
22
- "typescript": "^6.0.2"
12
+ "@git.zone/cli": "^3.0.1",
13
+ "@git.zone/tsbuild": "^4.4.2",
14
+ "@git.zone/tsrun": "^2.0.6",
15
+ "@git.zone/tstest": "^3.6.7",
16
+ "@types/node": "26.1.1",
17
+ "typescript": "^7.0.2"
23
18
  },
24
19
  "dependencies": {
25
- "@push.rocks/smartlog": "^3.2.1",
26
- "@push.rocks/smartpromise": "^4.2.3"
20
+ "@push.rocks/smartlog": "^3.2.2",
21
+ "@push.rocks/smartpromise": "^4.2.5"
27
22
  },
28
23
  "files": [
29
24
  "ts/**/*",
@@ -53,5 +48,11 @@
53
48
  },
54
49
  "bugs": {
55
50
  "url": "https://code.foss.global/push.rocks/smartnftables/issues"
51
+ },
52
+ "scripts": {
53
+ "test": "(tstest test/**/test*.ts --verbose --timeout 60)",
54
+ "build": "(tsbuild tsfolders)",
55
+ "format": "(gitzone format)",
56
+ "buildDocs": "tsdoc"
56
57
  }
57
- }
58
+ }
package/readme.md CHANGED
@@ -146,6 +146,8 @@ await nft.firewall.addRule('allow-ssh', {
146
146
  await nft.firewall.removeRule('allow-ssh');
147
147
  ```
148
148
 
149
+ When `sourcePort` or `destPort` is provided without `protocol`, TCP is used by default. Protocol-only rules match the specified layer-4 protocol.
150
+
149
151
  #### Block an IP
150
152
 
151
153
  ```typescript
@@ -337,7 +339,7 @@ All interfaces and types are fully exported for use in your own code:
337
339
 
338
340
  ## License and Legal Information
339
341
 
340
- This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the [LICENSE](./LICENSE) file.
342
+ This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the [repository license file](./license).
341
343
 
342
344
  **Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.
343
345
 
@@ -349,7 +351,7 @@ Use of these trademarks must comply with Task Venture Capital GmbH's Trademark G
349
351
 
350
352
  ### Company Information
351
353
 
352
- Task Venture Capital GmbH
354
+ Task Venture Capital GmbH<br>
353
355
  Registered at District Court Bremen HRB 35230 HB, Germany
354
356
 
355
357
  For any legal inquiries or further information, please contact us via email at hello@task.vc.
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@push.rocks/smartnftables',
6
- version: '1.1.0',
6
+ version: '1.2.1',
7
7
  description: 'A TypeScript module for managing nftables rules including NAT, firewall, and rate limiting with a high-level API.'
8
8
  }
package/ts/index.ts CHANGED
@@ -52,7 +52,9 @@ export type {
52
52
  INftRateLimitRule,
53
53
  INftConnectionRateRule,
54
54
  INftFirewallRule,
55
+ INftIPSetBlockOptions,
55
56
  INftIPSetConfig,
57
+ INftCleanupOptions,
56
58
  INftRuleGroup,
57
59
  ISmartNftablesOptions,
58
60
  INftStatus,
@@ -2,6 +2,75 @@ import * as plugins from './plugins.js';
2
2
 
3
3
  const execFile = plugins.util.promisify(plugins.childProcess.execFile);
4
4
 
5
+ export function normalizeNftCommand(command: string): string {
6
+ let input = command.trim();
7
+ if (!input) {
8
+ throw new Error('Invalid nft command: command is empty');
9
+ }
10
+ if (input === 'nft') {
11
+ throw new Error('Invalid nft command: command is empty');
12
+ }
13
+ if (/^nft\s/.test(input)) {
14
+ input = input.slice(3).trimStart();
15
+ }
16
+
17
+ let normalized = '';
18
+ let quote: 'single' | 'double' | undefined;
19
+
20
+ for (let index = 0; index < input.length; index++) {
21
+ const character = input[index];
22
+
23
+ if (quote === 'single') {
24
+ normalized += character;
25
+ if (character === "'") {
26
+ quote = undefined;
27
+ }
28
+ continue;
29
+ }
30
+
31
+ if (quote === 'double') {
32
+ normalized += character;
33
+ if (character === '"') {
34
+ quote = undefined;
35
+ continue;
36
+ }
37
+ if (character === '\\') {
38
+ const nextCharacter = input[index + 1];
39
+ if (nextCharacter === undefined) {
40
+ throw new Error('Invalid nft command: trailing escape in double-quoted string');
41
+ }
42
+ normalized += nextCharacter;
43
+ index++;
44
+ continue;
45
+ }
46
+ continue;
47
+ }
48
+
49
+ if (character === "'") {
50
+ quote = 'single';
51
+ } else if (character === '"') {
52
+ quote = 'double';
53
+ } else if (character === '\\') {
54
+ const nextCharacter = input[index + 1];
55
+ if (nextCharacter === undefined) {
56
+ throw new Error('Invalid nft command: trailing escape');
57
+ }
58
+ if (nextCharacter === ';') {
59
+ normalized += ';';
60
+ index++;
61
+ continue;
62
+ }
63
+ }
64
+
65
+ normalized += character;
66
+ }
67
+
68
+ if (quote) {
69
+ throw new Error(`Invalid nft command: unterminated ${quote}-quoted string`);
70
+ }
71
+ return normalized;
72
+ }
73
+
5
74
  export class NftNotRootError extends Error {
6
75
  constructor() {
7
76
  super('Not running as root — nftables commands require root privileges');
@@ -42,14 +111,12 @@ export class NftExecutor {
42
111
  * Returns stdout on success.
43
112
  */
44
113
  public async exec(command: string): Promise<string> {
114
+ const normalizedCommand = normalizeNftCommand(command);
45
115
  if (this.dryRun) {
46
116
  return '';
47
117
  }
48
118
 
49
- // Strip "nft " prefix if present
50
- const args = command.startsWith('nft ') ? command.slice(4) : command;
51
-
52
- const { stdout } = await execFile('nft', args.split(/\s+/));
119
+ const { stdout } = await execFile('nft', [normalizedCommand]);
53
120
  return stdout;
54
121
  }
55
122
 
@@ -1,5 +1,5 @@
1
1
  import type { SmartNftables } from './nft.manager.js';
2
- import type { INftFirewallRule, INftIPSetConfig } from './nft.types.js';
2
+ import type { INftFirewallRule, INftIPSetBlockOptions, INftIPSetConfig } from './nft.types.js';
3
3
  import {
4
4
  buildFirewallRule,
5
5
  buildIPSetCreate,
@@ -82,6 +82,41 @@ export class FirewallManager {
82
82
  });
83
83
  }
84
84
 
85
+ /**
86
+ * Convenience: block many IPs or CIDR subnets using one nftables set and
87
+ * one matching drop rule. This is substantially cheaper than one rule per IP.
88
+ */
89
+ public async blockIPSet(groupId: string, options: INftIPSetBlockOptions): Promise<void> {
90
+ const ips = [...new Set(options.ips)].filter(Boolean).sort();
91
+ if (ips.length === 0) {
92
+ return;
93
+ }
94
+
95
+ await this.parent.ensureFilterChains();
96
+
97
+ const setName = options.setName ?? `${groupId.replace(/[^a-zA-Z0-9_]/g, '_')}_set`;
98
+ const setType = options.type ?? (this.parent.family === 'ip6' ? 'ipv6_addr' : 'ipv4_addr');
99
+ const needsIntervalSet = ips.some((ip) => ip.includes('/'));
100
+ const direction = options.direction ?? 'input';
101
+ const commands = [
102
+ ...buildIPSetCreate(this.parent.tableName, this.parent.family, {
103
+ name: setName,
104
+ type: setType,
105
+ elements: ips,
106
+ interval: needsIntervalSet,
107
+ }),
108
+ ...buildIPSetMatchRule(this.parent.tableName, this.parent.family, {
109
+ setName,
110
+ direction,
111
+ matchField: 'saddr',
112
+ action: 'drop',
113
+ comment: options.comment,
114
+ }),
115
+ ];
116
+
117
+ await this.parent.applyRuleGroup(`fw:blockset:${groupId}`, commands);
118
+ }
119
+
85
120
  /**
86
121
  * Convenience: allow only specific IPs on a port.
87
122
  * Adds accept rules for each IP, then a drop rule for everything else on that port.
package/ts/nft.manager.ts CHANGED
@@ -3,7 +3,7 @@ import { buildTableSetup, buildFilterChains, buildTableCleanup } from './nft.rul
3
3
  import { NatManager } from './nft.manager.nat.js';
4
4
  import { FirewallManager } from './nft.manager.firewall.js';
5
5
  import { RateLimitManager } from './nft.manager.ratelimit.js';
6
- import type { TNftFamily, INftRuleGroup, INftStatus, ISmartNftablesOptions } from './nft.types.js';
6
+ import type { TNftFamily, INftCleanupOptions, INftRuleGroup, INftStatus, ISmartNftablesOptions } from './nft.types.js';
7
7
 
8
8
  /**
9
9
  * SmartNftables — high-level facade for managing nftables rules.
@@ -122,8 +122,8 @@ export class SmartNftables {
122
122
  /**
123
123
  * Delete the entire nftables table and clear all tracking.
124
124
  */
125
- public async cleanup(): Promise<void> {
126
- if (this.executor.isRoot() && this.initialized) {
125
+ public async cleanup(options: INftCleanupOptions = {}): Promise<void> {
126
+ if (this.executor.isRoot() && (this.initialized || options.force)) {
127
127
  const commands = buildTableCleanup(this.tableName, this.family);
128
128
  await this.executor.execBatch(commands, { continueOnError: true });
129
129
  }
@@ -138,7 +138,7 @@ export class SmartNftables {
138
138
  * Returns false if not root, not initialized, or the table was removed externally.
139
139
  */
140
140
  public async tableExists(): Promise<boolean> {
141
- if (!this.executor.isRoot() || !this.initialized) {
141
+ if (!this.executor.isRoot()) {
142
142
  return false;
143
143
  }
144
144
  try {
@@ -1,5 +1,9 @@
1
1
  import type { TNftFamily, INftFirewallRule, INftIPSetConfig } from './nft.types.js';
2
2
 
3
+ function escapeNftString(value: string): string {
4
+ return value.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
5
+ }
6
+
3
7
  /**
4
8
  * Build an nft firewall rule for input/output/forward chains.
5
9
  */
@@ -17,14 +21,17 @@ export function buildFirewallRule(
17
21
  }
18
22
 
19
23
  // Protocol and port matching
20
- const protocols = expandProtocols(rule.protocol);
24
+ const hasPort = rule.sourcePort != null || rule.destPort != null;
25
+ const protocols = expandProtocols(rule.protocol ?? (hasPort ? 'tcp' : undefined));
26
+ const protocolVariants = protocols.length > 0 ? protocols : [undefined];
21
27
  const commands: string[] = [];
22
28
 
23
- for (const proto of protocols) {
29
+ for (const proto of protocolVariants) {
24
30
  const ruleParts = [...parts];
31
+ const portProtocol = proto ?? 'tcp';
25
32
 
26
- if (rule.protocol) {
27
- ruleParts.push(proto);
33
+ if (proto && !hasPort) {
34
+ ruleParts.push(`meta l4proto ${proto}`);
28
35
  }
29
36
 
30
37
  if (rule.sourceIP) {
@@ -36,39 +43,17 @@ export function buildFirewallRule(
36
43
  }
37
44
 
38
45
  if (rule.sourcePort != null) {
39
- ruleParts.push(`${proto} sport ${rule.sourcePort}`);
46
+ ruleParts.push(`${portProtocol} sport ${rule.sourcePort}`);
40
47
  }
41
48
 
42
49
  if (rule.destPort != null) {
43
- // If protocol wasn't explicitly set but we have a port, we need the protocol
44
- if (!rule.protocol) {
45
- ruleParts.push(`tcp dport ${rule.destPort}`);
46
- } else {
47
- ruleParts.push(`${proto} dport ${rule.destPort}`);
48
- }
49
- }
50
-
51
- if (rule.comment) {
52
- ruleParts.push(`comment "${rule.comment}"`);
50
+ ruleParts.push(`${portProtocol} dport ${rule.destPort}`);
53
51
  }
54
52
 
55
53
  ruleParts.push(rule.action);
56
- commands.push(ruleParts.join(' '));
57
- }
58
-
59
- // If no protocol expansion needed (no protocol-specific fields)
60
- if (commands.length === 0) {
61
- const ruleParts = [...parts];
62
- if (rule.sourceIP) {
63
- ruleParts.push(`ip saddr ${rule.sourceIP}`);
64
- }
65
- if (rule.destIP) {
66
- ruleParts.push(`ip daddr ${rule.destIP}`);
67
- }
68
54
  if (rule.comment) {
69
- ruleParts.push(`comment "${rule.comment}"`);
55
+ ruleParts.push(`comment "${escapeNftString(rule.comment)}"`);
70
56
  }
71
- ruleParts.push(rule.action);
72
57
  commands.push(ruleParts.join(' '));
73
58
  }
74
59
 
@@ -84,10 +69,11 @@ export function buildIPSetCreate(
84
69
  config: INftIPSetConfig,
85
70
  ): string[] {
86
71
  const commands: string[] = [];
72
+ const flags = config.interval ? ' flags interval \\;' : '';
87
73
 
88
74
  // Create the set
89
75
  commands.push(
90
- `nft add set ${family} ${tableName} ${config.name} { type ${config.type} \\; }`
76
+ `nft add set ${family} ${tableName} ${config.name} { type ${config.type} \\;${flags} }`
91
77
  );
92
78
 
93
79
  // Add initial elements if provided
@@ -154,10 +140,12 @@ export function buildIPSetMatchRule(
154
140
  direction: 'input' | 'output' | 'forward';
155
141
  matchField: 'saddr' | 'daddr';
156
142
  action: 'accept' | 'drop' | 'reject';
143
+ comment?: string;
157
144
  },
158
145
  ): string[] {
146
+ const comment = options.comment ? ` comment "${escapeNftString(options.comment)}"` : '';
159
147
  return [
160
- `nft add rule ${family} ${tableName} ${options.direction} ip ${options.matchField} @${options.setName} ${options.action}`
148
+ `nft add rule ${family} ${tableName} ${options.direction} ip ${options.matchField} @${options.setName} ${options.action}${comment}`
161
149
  ];
162
150
  }
163
151
 
package/ts/nft.types.ts CHANGED
@@ -73,9 +73,29 @@ export interface INftIPSetConfig {
73
73
  name: string;
74
74
  type: 'ipv4_addr' | 'ipv6_addr' | 'inet_service';
75
75
  elements?: string[];
76
+ /** Enable interval sets so CIDR/range elements can be stored. */
77
+ interval?: boolean;
76
78
  comment?: string;
77
79
  }
78
80
 
81
+ export interface INftIPSetBlockOptions {
82
+ /** Name of the nftables set to create and match against. */
83
+ setName?: string;
84
+ /** IPs or CIDR ranges to add to the set. */
85
+ ips: string[];
86
+ /** Chain to apply the block rule to. Default: input. */
87
+ direction?: 'input' | 'forward';
88
+ /** Set type. Defaults to ipv4_addr for the default ip family. */
89
+ type?: 'ipv4_addr' | 'ipv6_addr';
90
+ /** Optional rule comment. */
91
+ comment?: string;
92
+ }
93
+
94
+ export interface INftCleanupOptions {
95
+ /** Delete the table even when this process did not initialize it. */
96
+ force?: boolean;
97
+ }
98
+
79
99
  // ─── Rule Group (tracking unit) ───────────────────────────────────
80
100
  export interface INftRuleGroup {
81
101
  id: string;