@push.rocks/smartnftables 1.0.1 → 1.2.0

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,20 @@
1
1
  # Changelog
2
2
 
3
+ ## 2026-04-26 - 1.2.0 - feat(firewall)
4
+ add IP set blocking convenience API with CIDR interval support and optional rule comments
5
+
6
+ - introduces firewall.blockIPSet() to create a set, populate it with IPs or CIDR ranges, and apply a single drop rule
7
+ - adds interval set support so CIDR entries can be stored in nftables sets
8
+ - supports optional comments on generated IP set match rules
9
+ - adds forced cleanup support and allows table existence checks without requiring prior initialization
10
+
11
+ ## 2026-03-30 - 1.1.0 - feat(nft)
12
+ add source IP filtering for DNAT rules and expose table existence checks
13
+
14
+ - Adds an optional sourceIP field to NAT rule definitions to restrict DNAT rules to matching source addresses or subnets.
15
+ - Updates DNAT rule generation to include an ip saddr match when sourceIP is provided.
16
+ - Introduces a tableExists() manager method to detect whether the managed nftables table is still present in the kernel.
17
+
3
18
  ## 2026-03-26 - 1.0.1 - fix(repo)
4
19
  no changes to commit
5
20
 
@@ -3,7 +3,7 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@push.rocks/smartnftables',
6
- version: '1.0.1',
6
+ version: '1.2.0',
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';
@@ -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,12 @@ 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
+ /**
56
+ * Check if the managed nftables table exists in the kernel.
57
+ * Returns false if not root, not initialized, or the table was removed externally.
58
+ */
59
+ tableExists(): Promise<boolean>;
55
60
  /**
56
61
  * Get status report of the managed nftables state.
57
62
  */
@@ -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
  }
@@ -108,6 +108,22 @@ export class SmartNftables {
108
108
  this.initialized = false;
109
109
  this.hasFilterChains = false;
110
110
  }
111
+ /**
112
+ * Check if the managed nftables table exists in the kernel.
113
+ * Returns false if not root, not initialized, or the table was removed externally.
114
+ */
115
+ async tableExists() {
116
+ if (!this.executor.isRoot()) {
117
+ return false;
118
+ }
119
+ try {
120
+ await this.executor.exec(`nft list table ${this.family} ${this.tableName}`);
121
+ return true;
122
+ }
123
+ catch {
124
+ return false;
125
+ }
126
+ }
111
127
  /**
112
128
  * Get status report of the managed nftables state.
113
129
  */
@@ -129,4 +145,4 @@ export class SmartNftables {
129
145
  };
130
146
  }
131
147
  }
132
- //# 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[];
@@ -62,8 +62,9 @@ export function buildFirewallRule(tableName, family, rule) {
62
62
  */
63
63
  export function buildIPSetCreate(tableName, family, config) {
64
64
  const commands = [];
65
+ const flags = config.interval ? ' flags interval \\;' : '';
65
66
  // Create the set
66
- commands.push(`nft add set ${family} ${tableName} ${config.name} { type ${config.type} \\; }`);
67
+ commands.push(`nft add set ${family} ${tableName} ${config.name} { type ${config.type} \\;${flags} }`);
67
68
  // Add initial elements if provided
68
69
  if (config.elements && config.elements.length > 0) {
69
70
  commands.push(`nft add element ${family} ${tableName} ${config.name} { ${config.elements.join(', ')} }`);
@@ -102,8 +103,9 @@ export function buildIPSetDelete(tableName, family, setName) {
102
103
  * Build a rule that matches against a named set.
103
104
  */
104
105
  export function buildIPSetMatchRule(tableName, family, options) {
106
+ const comment = options.comment ? ` comment "${options.comment}"` : '';
105
107
  return [
106
- `nft add rule ${family} ${tableName} ${options.direction} ip ${options.matchField} @${options.setName} ${options.action}`
108
+ `nft add rule ${family} ${tableName} ${options.direction} ip ${options.matchField} @${options.setName}${comment} ${options.action}`
107
109
  ];
108
110
  }
109
111
  // ─── Internal helpers ─────────────────────────────────────────────
@@ -116,4 +118,4 @@ function expandProtocols(protocol) {
116
118
  case 'both': return ['tcp', 'udp'];
117
119
  }
118
120
  }
119
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibmZ0LnJ1bGVidWlsZGVyLmZpcmV3YWxsLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvbmZ0LnJ1bGVidWlsZGVyLmZpcmV3YWxsLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUVBOztHQUVHO0FBQ0gsTUFBTSxVQUFVLGlCQUFpQixDQUMvQixTQUFpQixFQUNqQixNQUFrQixFQUNsQixJQUFzQjtJQUV0QixNQUFNLEtBQUssR0FBRyxJQUFJLENBQUMsU0FBUyxDQUFDO0lBQzdCLE1BQU0sS0FBSyxHQUFhLENBQUMsZ0JBQWdCLE1BQU0sSUFBSSxTQUFTLElBQUksS0FBSyxFQUFFLENBQUMsQ0FBQztJQUV6RSw2QkFBNkI7SUFDN0IsSUFBSSxJQUFJLENBQUMsUUFBUSxJQUFJLElBQUksQ0FBQyxRQUFRLENBQUMsTUFBTSxHQUFHLENBQUMsRUFBRSxDQUFDO1FBQzlDLEtBQUssQ0FBQyxJQUFJLENBQUMsY0FBYyxJQUFJLENBQUMsUUFBUSxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLENBQUM7SUFDekQsQ0FBQztJQUVELDZCQUE2QjtJQUM3QixNQUFNLFNBQVMsR0FBRyxlQUFlLENBQUMsSUFBSSxDQUFDLFFBQVEsQ0FBQyxDQUFDO0lBQ2pELE1BQU0sUUFBUSxHQUFhLEVBQUUsQ0FBQztJQUU5QixLQUFLLE1BQU0sS0FBSyxJQUFJLFNBQVMsRUFBRSxDQUFDO1FBQzlCLE1BQU0sU0FBUyxHQUFHLENBQUMsR0FBRyxLQUFLLENBQUMsQ0FBQztRQUU3QixJQUFJLElBQUksQ0FBQyxRQUFRLEVBQUUsQ0FBQztZQUNsQixTQUFTLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxDQUFDO1FBQ3hCLENBQUM7UUFFRCxJQUFJLElBQUksQ0FBQyxRQUFRLEVBQUUsQ0FBQztZQUNsQixTQUFTLENBQUMsSUFBSSxDQUFDLFlBQVksSUFBSSxDQUFDLFFBQVEsRUFBRSxDQUFDLENBQUM7UUFDOUMsQ0FBQztRQUVELElBQUksSUFB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
121
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibmZ0LnJ1bGVidWlsZGVyLmZpcmV3YWxsLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvbmZ0LnJ1bGVidWlsZGVyLmZpcmV3YWxsLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUVBOztHQUVHO0FBQ0gsTUFBTSxVQUFVLGlCQUFpQixDQUMvQixTQUFpQixFQUNqQixNQUFrQixFQUNsQixJQUFzQjtJQUV0QixNQUFNLEtBQUssR0FBRyxJQUFJLENBQUMsU0FBUyxDQUFDO0lBQzdCLE1BQU0sS0FBSyxHQUFhLENBQUMsZ0JBQWdCLE1BQU0sSUFBSSxTQUFTLElBQUksS0FBSyxFQUFFLENBQUMsQ0FBQztJQUV6RSw2QkFBNkI7SUFDN0IsSUFBSSxJQUFJLENBQUMsUUFBUSxJQUFJLElBQUksQ0FBQyxRQUFRLENBQUMsTUFBTSxHQUFHLENBQUMsRUFBRSxDQUFDO1FBQzlDLEtBQUssQ0FBQyxJQUFJLENBQUMsY0FBYyxJQUFJLENBQUMsUUFBUSxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLENBQUM7SUFDekQsQ0FBQztJQUVELDZCQUE2QjtJQUM3QixNQUFNLFNBQVMsR0FBRyxlQUFlLENBQUMsSUFBSSxDQUFDLFFBQVEsQ0FBQyxDQUFDO0lBQ2pELE1BQU0sUUFBUSxHQUFhLEVBQUUsQ0FBQztJQUU5QixLQUFLLE1BQU0sS0FBSyxJQUFJLFNBQVMsRUFBRSxDQUFDO1FBQzlCLE1BQU0sU0FBUyxHQUFHLENBQUMsR0FBRyxLQUFLLENBQUMsQ0FBQztRQUU3QixJQUFJLElBQUksQ0FBQyxRQUFRLEVBQUUsQ0FBQztZQUNsQixTQUFTLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxDQUFDO1FBQ3hCLENBQUM7UUFFRCxJQUFJLElBQUksQ0FBQyxRQUFRLEVBQUUsQ0FBQztZQUNsQixTQUFTLENBQUMsSUFBSSxDQUFDLFlBQVksSUFBSSxDQUFDLFFBQVEsRUFBRSxDQUFDLENBQUM7UUFDOUMsQ0FBQztRQUVELElBQUksSUFB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
@@ -18,7 +18,8 @@ export function buildDnatRules(tableName, family, rule) {
18
18
  const commands = [];
19
19
  for (const proto of protocols) {
20
20
  // DNAT rule
21
- commands.push(`nft add rule ${family} ${tableName} prerouting ${proto} dport ${rule.sourcePort} dnat to ${rule.targetHost}:${rule.targetPort}`);
21
+ const saddrFilter = rule.sourceIP ? `ip saddr ${rule.sourceIP} ` : '';
22
+ commands.push(`nft add rule ${family} ${tableName} prerouting ${saddrFilter}${proto} dport ${rule.sourcePort} dnat to ${rule.targetHost}:${rule.targetPort}`);
22
23
  // Masquerade (SNAT) unless preserveSourceIP is set
23
24
  if (!rule.preserveSourceIP) {
24
25
  commands.push(`nft add rule ${family} ${tableName} postrouting ${proto} dport ${rule.targetPort} masquerade`);
@@ -48,4 +49,4 @@ export function buildMasqueradeRule(tableName, family, rule) {
48
49
  }
49
50
  return commands;
50
51
  }
51
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibmZ0LnJ1bGVidWlsZGVyLm5hdC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RzL25mdC5ydWxlYnVpbGRlci5uYXQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBRUE7O0dBRUc7QUFDSCxTQUFTLGVBQWUsQ0FBQyxRQUF1QjtJQUM5QyxRQUFRLFFBQVEsSUFBSSxLQUFLLEVBQUUsQ0FBQztRQUMxQixLQUFLLEtBQUssQ0FBQyxDQUFDLE9BQU8sQ0FBQyxLQUFLLENBQUMsQ0FBQztRQUMzQixLQUFLLEtBQUssQ0FBQyxDQUFDLE9BQU8sQ0FBQyxLQUFLLENBQUMsQ0FBQztRQUMzQixLQUFLLE1BQU0sQ0FBQyxDQUFDLE9BQU8sQ0FBQyxLQUFLLEVBQUUsS0FBSyxDQUFDLENBQUM7SUFDckMsQ0FBQztBQUNILENBQUM7QUFFRDs7OztHQUlHO0FBQ0gsTUFBTSxVQUFVLGNBQWMsQ0FDNUIsU0FBaUIsRUFDakIsTUFBa0IsRUFDbEIsSUFBa0I7SUFFbEIsTUFBTSxTQUFTLEdBQUcsZUFBZSxDQUFDLElBQUksQ0FBQyxRQUFRLENBQUMsQ0FBQztJQUNqRCxNQUFNLFFBQVEsR0FBYSxFQUFFLENBQUM7SUFFOUIsS0FBSyxNQUFNLEtBQUssSUFBSSxTQUFTLEVBQUUsQ0FBQztRQUM5QixZQUFZO1FBQ1osUUFBUSxDQUFDLElBQUksQ0FDWCxnQkFBZ0IsTUFBTSxJQUFJLFNBQVMsZUFBZSxLQUFLLFVBQVUsSUFBSSxDQUFDLFVBQVUsWUFBWSxJQUFJLENBQUMsVUFBVSxJQUFJLElBQUksQ0FBQyxVQUFVLEVBQUUsQ0FDakksQ0FBQztRQUVGLG1EQUFtRDtRQUNuRCxJQUFJLENBQUMsSUFBSSxDQUFDLGdCQUFnQixFQUFFLENBQUM7WUFDM0IsUUFBUSxDQUFDLElBQUksQ0FDWCxnQkFBZ0IsTUFBTSxJQUFJLFNBQVMsZ0JBQWdCLEtBQUssVUFBVSxJQUFJLENBQUMsVUFBVSxhQUFhLENBQy9GLENBQUM7UUFDSixDQUFDO0lBQ0gsQ0FBQztJQUVELE9BQU8sUUFBUSxDQUFDO0FBQ2xCLENBQUM7QUFFRDs7R0FFRztBQUNILE1BQU0sVUFBVSxhQUFhLENBQzNCLFNBQWlCLEVBQ2pCLE1BQWtCLEVBQ2xCLElBQWtCO0lBRWxCLE1BQU0sU0FBUyxHQUFHLGVBQWUsQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFDLENBQUM7SUFDakQsTUFBTSxRQUFRLEdBQWEsRUFBRSxDQUFDO0lBRTlCLEtBQUssTUFBTSxLQUFLLElBQUksU0FBUyxFQUFFLENBQUM7UUFDOUIsUUFBUSxDQUFDLElBQUksQ0FDWCxnQkFBZ0IsTUFBTSxJQUFJLFNBQVMsZ0JBQWdCLEtBQUssVUFBVSxJQUFJLENBQUMsVUFBVSxZQUFZLElBQUksQ0FBQyxhQUFhLEVBQUUsQ0FDbEgsQ0FBQztJQUNKLENBQUM7SUFFRCxPQUFPLFFBQVEsQ0FBQztBQUNsQixDQUFDO0FBRUQ7O0dBRUc7QUFDSCxNQUFNLFVBQVUsbUJBQW1CLENBQ2pDLFNBQWlCLEVBQ2pCLE1BQWtCLEVBQ2xCLElBQXdCO0lBRXhCLE1BQU0sU0FBUyxHQUFHLGVBQWUsQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFDLENBQUM7SUFDakQsTUFBTSxRQUFRLEdBQWEsRUFBRSxDQUFDO0lBRTlCLEtBQUssTUFBTSxLQUFLLElBQUksU0FBUyxFQUFFLENBQUM7UUFDOUIsUUFBUSxDQUFDLElBQUksQ0FDWCxnQkFBZ0IsTUFBTSxJQUFJLFNBQVMsZ0JBQWdCLEtBQUssVUFBVSxJQUFJLENBQUMsVUFBVSxhQUFhLENBQy9GLENBQUM7SUFDSixDQUFDO0lBRUQsT0FBTyxRQUFRLENBQUM7QUFDbEIsQ0FBQyJ9
52
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibmZ0LnJ1bGVidWlsZGVyLm5hdC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RzL25mdC5ydWxlYnVpbGRlci5uYXQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBRUE7O0dBRUc7QUFDSCxTQUFTLGVBQWUsQ0FBQyxRQUF1QjtJQUM5QyxRQUFRLFFBQVEsSUFBSSxLQUFLLEVBQUUsQ0FBQztRQUMxQixLQUFLLEtBQUssQ0FBQyxDQUFDLE9BQU8sQ0FBQyxLQUFLLENBQUMsQ0FBQztRQUMzQixLQUFLLEtBQUssQ0FBQyxDQUFDLE9BQU8sQ0FBQyxLQUFLLENBQUMsQ0FBQztRQUMzQixLQUFLLE1BQU0sQ0FBQyxDQUFDLE9BQU8sQ0FBQyxLQUFLLEVBQUUsS0FBSyxDQUFDLENBQUM7SUFDckMsQ0FBQztBQUNILENBQUM7QUFFRDs7OztHQUlHO0FBQ0gsTUFBTSxVQUFVLGNBQWMsQ0FDNUIsU0FBaUIsRUFDakIsTUFBa0IsRUFDbEIsSUFBa0I7SUFFbEIsTUFBTSxTQUFTLEdBQUcsZUFBZSxDQUFDLElBQUksQ0FBQyxRQUFRLENBQUMsQ0FBQztJQUNqRCxNQUFNLFFBQVEsR0FBYSxFQUFFLENBQUM7SUFFOUIsS0FBSyxNQUFNLEtBQUssSUFBSSxTQUFTLEVBQUUsQ0FBQztRQUM5QixZQUFZO1FBQ1osTUFBTSxXQUFXLEdBQUcsSUFBSSxDQUFDLFFBQVEsQ0FBQyxDQUFDLENBQUMsWUFBWSxJQUFJLENBQUMsUUFBUSxHQUFHLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQztRQUN0RSxRQUFRLENBQUMsSUFBSSxDQUNYLGdCQUFnQixNQUFNLElBQUksU0FBUyxlQUFlLFdBQVcsR0FBRyxLQUFLLFVBQVUsSUFBSSxDQUFDLFVBQVUsWUFBWSxJQUFJLENBQUMsVUFBVSxJQUFJLElBQUksQ0FBQyxVQUFVLEVBQUUsQ0FDL0ksQ0FBQztRQUVGLG1EQUFtRDtRQUNuRCxJQUFJLENBQUMsSUFBSSxDQUFDLGdCQUFnQixFQUFFLENBQUM7WUFDM0IsUUFBUSxDQUFDLElBQUksQ0FDWCxnQkFBZ0IsTUFBTSxJQUFJLFNBQVMsZ0JBQWdCLEtBQUssVUFBVSxJQUFJLENBQUMsVUFBVSxhQUFhLENBQy9GLENBQUM7UUFDSixDQUFDO0lBQ0gsQ0FBQztJQUVELE9BQU8sUUFBUSxDQUFDO0FBQ2xCLENBQUM7QUFFRDs7R0FFRztBQUNILE1BQU0sVUFBVSxhQUFhLENBQzNCLFNBQWlCLEVBQ2pCLE1BQWtCLEVBQ2xCLElBQWtCO0lBRWxCLE1BQU0sU0FBUyxHQUFHLGVBQWUsQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFDLENBQUM7SUFDakQsTUFBTSxRQUFRLEdBQWEsRUFBRSxDQUFDO0lBRTlCLEtBQUssTUFBTSxLQUFLLElBQUksU0FBUyxFQUFFLENBQUM7UUFDOUIsUUFBUSxDQUFDLElBQUksQ0FDWCxnQkFBZ0IsTUFBTSxJQUFJLFNBQVMsZ0JBQWdCLEtBQUssVUFBVSxJQUFJLENBQUMsVUFBVSxZQUFZLElBQUksQ0FBQyxhQUFhLEVBQUUsQ0FDbEgsQ0FBQztJQUNKLENBQUM7SUFFRCxPQUFPLFFBQVEsQ0FBQztBQUNsQixDQUFDO0FBRUQ7O0dBRUc7QUFDSCxNQUFNLFVBQVUsbUJBQW1CLENBQ2pDLFNBQWlCLEVBQ2pCLE1BQWtCLEVBQ2xCLElBQXdCO0lBRXhCLE1BQU0sU0FBUyxHQUFHLGVBQWUsQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFDLENBQUM7SUFDakQsTUFBTSxRQUFRLEdBQWEsRUFBRSxDQUFDO0lBRTlCLEtBQUssTUFBTSxLQUFLLElBQUksU0FBUyxFQUFFLENBQUM7UUFDOUIsUUFBUSxDQUFDLElBQUksQ0FDWCxnQkFBZ0IsTUFBTSxJQUFJLFNBQVMsZ0JBQWdCLEtBQUssVUFBVSxJQUFJLENBQUMsVUFBVSxhQUFhLENBQy9GLENBQUM7SUFDSixDQUFDO0lBRUQsT0FBTyxRQUFRLENBQUM7QUFDbEIsQ0FBQyJ9
@@ -11,6 +11,8 @@ export interface INftDnatRule {
11
11
  targetPort: number;
12
12
  protocol?: TNftProtocol;
13
13
  preserveSourceIP?: boolean;
14
+ /** Filter by source IP/subnet (e.g. '10.8.0.0/24'). Only matching traffic gets DNAT'd. */
15
+ sourceIP?: string;
14
16
  }
15
17
  export interface INftSnatRule {
16
18
  sourceAddress: string;
@@ -60,8 +62,26 @@ export interface INftIPSetConfig {
60
62
  name: string;
61
63
  type: 'ipv4_addr' | 'ipv6_addr' | 'inet_service';
62
64
  elements?: string[];
65
+ /** Enable interval sets so CIDR/range elements can be stored. */
66
+ interval?: boolean;
67
+ comment?: string;
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. */
63
79
  comment?: string;
64
80
  }
81
+ export interface INftCleanupOptions {
82
+ /** Delete the table even when this process did not initialize it. */
83
+ force?: boolean;
84
+ }
65
85
  export interface INftRuleGroup {
66
86
  id: string;
67
87
  commands: string[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@push.rocks/smartnftables",
3
- "version": "1.0.1",
3
+ "version": "1.2.0",
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",
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@push.rocks/smartnftables',
6
- version: '1.0.1',
6
+ version: '1.2.0',
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,
@@ -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
  }
@@ -133,6 +133,22 @@ export class SmartNftables {
133
133
  this.hasFilterChains = false;
134
134
  }
135
135
 
136
+ /**
137
+ * Check if the managed nftables table exists in the kernel.
138
+ * Returns false if not root, not initialized, or the table was removed externally.
139
+ */
140
+ public async tableExists(): Promise<boolean> {
141
+ if (!this.executor.isRoot()) {
142
+ return false;
143
+ }
144
+ try {
145
+ await this.executor.exec(`nft list table ${this.family} ${this.tableName}`);
146
+ return true;
147
+ } catch {
148
+ return false;
149
+ }
150
+ }
151
+
136
152
  /**
137
153
  * Get status report of the managed nftables state.
138
154
  */
@@ -84,10 +84,11 @@ export function buildIPSetCreate(
84
84
  config: INftIPSetConfig,
85
85
  ): string[] {
86
86
  const commands: string[] = [];
87
+ const flags = config.interval ? ' flags interval \\;' : '';
87
88
 
88
89
  // Create the set
89
90
  commands.push(
90
- `nft add set ${family} ${tableName} ${config.name} { type ${config.type} \\; }`
91
+ `nft add set ${family} ${tableName} ${config.name} { type ${config.type} \\;${flags} }`
91
92
  );
92
93
 
93
94
  // Add initial elements if provided
@@ -154,10 +155,12 @@ export function buildIPSetMatchRule(
154
155
  direction: 'input' | 'output' | 'forward';
155
156
  matchField: 'saddr' | 'daddr';
156
157
  action: 'accept' | 'drop' | 'reject';
158
+ comment?: string;
157
159
  },
158
160
  ): string[] {
161
+ const comment = options.comment ? ` comment "${options.comment}"` : '';
159
162
  return [
160
- `nft add rule ${family} ${tableName} ${options.direction} ip ${options.matchField} @${options.setName} ${options.action}`
163
+ `nft add rule ${family} ${tableName} ${options.direction} ip ${options.matchField} @${options.setName}${comment} ${options.action}`
161
164
  ];
162
165
  }
163
166
 
@@ -26,8 +26,9 @@ export function buildDnatRules(
26
26
 
27
27
  for (const proto of protocols) {
28
28
  // DNAT rule
29
+ const saddrFilter = rule.sourceIP ? `ip saddr ${rule.sourceIP} ` : '';
29
30
  commands.push(
30
- `nft add rule ${family} ${tableName} prerouting ${proto} dport ${rule.sourcePort} dnat to ${rule.targetHost}:${rule.targetPort}`
31
+ `nft add rule ${family} ${tableName} prerouting ${saddrFilter}${proto} dport ${rule.sourcePort} dnat to ${rule.targetHost}:${rule.targetPort}`
31
32
  );
32
33
 
33
34
  // Masquerade (SNAT) unless preserveSourceIP is set
package/ts/nft.types.ts CHANGED
@@ -14,6 +14,8 @@ export interface INftDnatRule {
14
14
  targetPort: number;
15
15
  protocol?: TNftProtocol;
16
16
  preserveSourceIP?: boolean;
17
+ /** Filter by source IP/subnet (e.g. '10.8.0.0/24'). Only matching traffic gets DNAT'd. */
18
+ sourceIP?: string;
17
19
  }
18
20
 
19
21
  export interface INftSnatRule {
@@ -71,9 +73,29 @@ export interface INftIPSetConfig {
71
73
  name: string;
72
74
  type: 'ipv4_addr' | 'ipv6_addr' | 'inet_service';
73
75
  elements?: string[];
76
+ /** Enable interval sets so CIDR/range elements can be stored. */
77
+ interval?: boolean;
78
+ comment?: string;
79
+ }
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. */
74
91
  comment?: string;
75
92
  }
76
93
 
94
+ export interface INftCleanupOptions {
95
+ /** Delete the table even when this process did not initialize it. */
96
+ force?: boolean;
97
+ }
98
+
77
99
  // ─── Rule Group (tracking unit) ───────────────────────────────────
78
100
  export interface INftRuleGroup {
79
101
  id: string;