nftables-napi 0.1.0 → 0.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/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # nftables-napi
2
2
 
3
- Native Node.js binding for nftables via libnftnl + libmnl. Manages IPv4/IPv6 tables with dynamic sets and timeout support through direct netlink communication — no shell commands, no `nft` CLI.
3
+ Native Node.js binding for nftables via libnftnl + libmnl. Manages IPv4/IPv6 firewall tables with dynamic IP sets, port blocking, named counters, and timeout support through direct netlink communication — no shell commands, no `nft` CLI.
4
4
 
5
- Requires Linux with `CAP_NET_ADMIN` or root.
5
+ Requires Linux kernel ≥ 5.7 with `CAP_NET_ADMIN` or root.
6
6
 
7
7
  ## Install
8
8
 
@@ -34,26 +34,44 @@ RUN apt-get update && apt-get install -y libnftnl13 libmnl0 && rm -rf /var/lib/a
34
34
  const { NftManager } = require("nftables-napi");
35
35
 
36
36
  const nft = new NftManager({
37
- tableName: "tablename",
38
- sets: ["blacklist", "droplist"],
37
+ tableName: "myfw",
38
+ sets: ["blacklist"],
39
+ outSets: ["blocklist"],
40
+ outPortSets: ["blocked_ports"],
39
41
  });
40
42
 
41
43
  await nft.createTable();
42
44
 
43
- // Add with timeout (seconds)
45
+ // ── IP blocking (input/forward) ──
46
+
44
47
  await nft.addAddress({ ip: "1.2.3.4", set: "blacklist", timeout: 1800 });
45
48
  await nft.addAddress({ ip: "2001:db8::1", set: "blacklist", timeout: 3600 });
46
-
47
- // Add permanent (no timeout)
48
- await nft.addAddress({ ip: "5.6.7.8", set: "droplist" });
49
-
50
- // Bulk add
51
49
  await nft.addAddresses({ ips: ["10.0.0.1", "10.0.0.2"], set: "blacklist", timeout: 7200 });
52
50
 
53
- // Remove
54
51
  await nft.removeAddress({ ip: "1.2.3.4", set: "blacklist" });
55
52
  await nft.removeAddresses({ ips: ["10.0.0.1", "10.0.0.2"], set: "blacklist" });
56
53
 
54
+ // ── IP blocking (output) ──
55
+
56
+ await nft.addAddress({ ip: "93.184.216.34", set: "blocklist" });
57
+ await nft.removeAddress({ ip: "93.184.216.34", set: "blocklist" });
58
+
59
+ // ── Port blocking (output, tcp/udp) ──
60
+
61
+ // Block port 80 for both TCP and UDP
62
+ await nft.addPort({ port: 80, set: "blocked_ports", timeout: 3600 });
63
+
64
+ // Block port 443 for TCP only
65
+ await nft.addPort({ port: 443, set: "blocked_ports", protocol: "tcp" });
66
+
67
+ // Bulk port operations
68
+ await nft.addPorts({ ports: [8080, 8443], set: "blocked_ports", protocol: "tcp" });
69
+ await nft.removePorts({ ports: [8080, 8443], set: "blocked_ports", protocol: "tcp" });
70
+
71
+ await nft.removePort({ port: 80, set: "blocked_ports" });
72
+
73
+ // ── Cleanup ──
74
+
57
75
  await nft.deleteTable();
58
76
  ```
59
77
 
@@ -61,23 +79,119 @@ await nft.deleteTable();
61
79
 
62
80
  ### `new NftManager(options)`
63
81
 
64
- | Option | Type | Required | Description |
65
- | ----------- | ---------- | -------- | ------------------------------------------------------------------------------------------------ |
66
- | `tableName` | `string` | Yes | Base table name (IPv6 table auto-appends `'6'`) |
67
- | `sets` | `string[]` | Yes | Set names (1+, unique, non-empty). IPv6 sets auto-append `'6'`. Log prefix: `'{name}: '` |
82
+ | Option | Type | Required | Description |
83
+ | --- | --- | --- | --- |
84
+ | `tableName` | `string` | Yes | Base table name. IPv6 table auto-appends `'6'`. |
85
+ | `sets` | `string[]` | Yes | Input/forward IP set names (1). Block by **source** address on input and forward chains. Rules: log + named counter + drop. IPv6 sets auto-append `'6'`. |
86
+ | `outSets` | `string[]` | No | Output IP set names. Block by **destination** address on output chain. Rules: named counter + drop (no log). IPv6 sets auto-append `'6'`. |
87
+ | `outPortSets` | `string[]` | No | Output port set names. Block by **destination port** (TCP/UDP) on output chain using concatenated `inet_proto . inet_service` sets. Ports are added to both IPv4 and IPv6 tables. IPv6 sets auto-append `'6'`. |
68
88
 
69
89
  ### Methods
70
90
 
71
- All methods return `Promise<void>`.
91
+ All methods return `Promise<void>` and throw on error.
92
+
93
+ #### Table management
94
+
95
+ | Method | Description |
96
+ | --- | --- |
97
+ | `createTable()` | Create IPv4/IPv6 tables with all configured sets, chains, named counters, and filter rules. Idempotent — deletes existing tables first. |
98
+ | `deleteTable()` | Delete both tables. Idempotent — no error if tables don't exist. |
99
+
100
+ #### IP address operations
101
+
102
+ Work with both `sets` (input/forward) and `outSets` (output).
72
103
 
73
- | Method | Description |
74
- | -------------------------------------- | --------------------------------------------------------------------- |
75
- | `createTable()` | Create IPv4/IPv6 tables with all configured sets and filter chains. Idempotent. |
76
- | `deleteTable()` | Delete both tables. Idempotent. |
77
- | `addAddress({ ip, set, timeout? })` | Add IP to set. `timeout` in seconds, omit for permanent. |
78
- | `removeAddress({ ip, set })` | Remove IP from set. Idempotent. |
79
- | `addAddresses({ ips, set, timeout? })` | Bulk add to set. Chunked for efficient netlink communication. |
80
- | `removeAddresses({ ips, set })` | Bulk remove from set. Idempotent. |
104
+ | Method | Description |
105
+ | --- | --- |
106
+ | `addAddress({ ip, set, timeout? })` | Add IP to set. Auto-detects IPv4/IPv6. `timeout` in seconds, omit for permanent. |
107
+ | `removeAddress({ ip, set })` | Remove IP from set. Idempotent. |
108
+ | `addAddresses({ ips, set, timeout? })` | Bulk add. Chunked internally for efficient netlink communication. Empty array is a no-op. |
109
+ | `removeAddresses({ ips, set })` | Bulk remove. Idempotent. Empty array is a no-op. |
110
+
111
+ #### Port operations
112
+
113
+ Work with `outPortSets` only. Ports are added to both IPv4 and IPv6 tables.
114
+
115
+ | Method | Description |
116
+ | --- | --- |
117
+ | `addPort({ port, set, protocol?, timeout? })` | Add port to set. `protocol`: `'tcp'`, `'udp'`, or omit for both. `timeout` in seconds. |
118
+ | `removePort({ port, set, protocol? })` | Remove port from set. Idempotent. |
119
+ | `addPorts({ ports, set, protocol?, timeout? })` | Bulk add ports. Empty array is a no-op. |
120
+ | `removePorts({ ports, set, protocol? })` | Bulk remove ports. Idempotent. Empty array is a no-op. |
121
+
122
+ ### What `createTable()` builds
123
+
124
+ For a config with `sets: ["bl"]`, `outSets: ["out"]`, `outPortSets: ["ports"]`:
125
+
126
+ ```
127
+ table ip myfw {
128
+ counter "processed" { packets 0 bytes 0 }
129
+ counter "bl" { packets 0 bytes 0 }
130
+ counter "out" { packets 0 bytes 0 }
131
+ counter "ports" { packets 0 bytes 0 }
132
+
133
+ set bl {
134
+ type ipv4_addr
135
+ flags timeout
136
+ counter
137
+ }
138
+
139
+ set out {
140
+ type ipv4_addr
141
+ flags timeout
142
+ counter
143
+ }
144
+
145
+ set ports {
146
+ type inet_proto . inet_service
147
+ flags timeout
148
+ counter
149
+ }
150
+
151
+ chain input {
152
+ type filter hook input priority -10; policy accept;
153
+ counter name "processed"
154
+ ip saddr @bl log prefix "bl: " counter name "bl" drop
155
+ }
156
+
157
+ chain forward {
158
+ type filter hook forward priority -10; policy accept;
159
+ counter name "processed"
160
+ ip saddr @bl log prefix "bl: " counter name "bl" drop
161
+ }
162
+
163
+ chain output {
164
+ type filter hook output priority -10; policy accept;
165
+ ip daddr @out counter name "out" drop
166
+ meta l4proto . th dport @ports counter name "ports" drop
167
+ }
168
+ }
169
+ ```
170
+
171
+ IPv6 table (`myfw6`) mirrors the same structure with `ipv6_addr` sets and corresponding offsets.
172
+
173
+ ## Kernel compatibility
174
+
175
+ Minimum: **Linux 5.7**
176
+
177
+ | Feature | Kernel | Used for |
178
+ | --- | --- | --- |
179
+ | nftables core | 3.13 | tables, chains, sets, rules |
180
+ | Set timeouts | 4.1 | element expiration |
181
+ | Named counters | 4.10 | traffic accounting |
182
+ | Concatenated sets | 5.6 | port blocking (`inet_proto . inet_service`) |
183
+ | Per-element set expressions | 5.7 | per-element counters |
184
+
185
+ | Distro | Kernel | Compatible |
186
+ | --- | --- | --- |
187
+ | Ubuntu 22.04+ | 5.15+ | Yes |
188
+ | Ubuntu 20.04 (HWE) | 5.15 | Yes |
189
+ | Ubuntu 20.04 (GA) | 5.4 | No |
190
+ | Debian 11+ | 5.10+ | Yes |
191
+ | Debian 10 | 4.19 | No |
192
+ | RHEL / Rocky 9 | 5.14 | Yes |
193
+ | RHEL / Rocky 8 | 4.18 | No |
194
+ | Alpine 3.16+ | 5.15+ | Yes |
81
195
 
82
196
  ## Building from source
83
197
 
@@ -85,9 +199,15 @@ All methods return `Promise<void>`.
85
199
  # Dependencies (Debian/Ubuntu)
86
200
  sudo apt install pkg-config libnftnl-dev libmnl-dev build-essential
87
201
 
202
+ # Dependencies (Alpine)
203
+ apk add pkgconfig libnftnl-dev libmnl-dev build-base python3
204
+
88
205
  # Build
89
206
  npm run build
90
207
 
208
+ # Run tests (requires root / CAP_NET_ADMIN)
209
+ npm test
210
+
91
211
  # Prebuild for current platform
92
212
  npx prebuildify --napi --strip
93
213
 
package/binding.gyp CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "targets": [
3
3
  {
4
- "target_name": "remnawave_nft",
4
+ "target_name": "nftables_napi",
5
5
  "sources": [
6
6
  "src/addon.cpp",
7
7
  "src/nft_manager.cpp",
@@ -28,7 +28,9 @@
28
28
  "-Wall",
29
29
  "-Wextra",
30
30
  "-Wpedantic",
31
- "-Wno-unused-parameter"
31
+ "-Wno-unused-parameter",
32
+ "-D_FORTIFY_SOURCE=2",
33
+ "-fstack-protector-strong"
32
34
  ],
33
35
  "ldflags": [
34
36
  "-Wl,-z,relro",
package/lib/index.d.ts CHANGED
@@ -4,21 +4,38 @@
4
4
  * Requires CAP_NET_ADMIN or root privileges.
5
5
  */
6
6
 
7
- /** Constructor options. All fields are required — no defaults. */
7
+ /** Constructor options. */
8
8
  export interface NftManagerOptions {
9
9
  /** Base table name. IPv6 table auto-appends '6'. */
10
10
  tableName: string;
11
- /** Set names. At least 1, no duplicates, non-empty strings. IPv6 sets auto-append '6'. */
11
+ /**
12
+ * Input/forward IP set names (≥1 required). Block by source address.
13
+ * Rules: log prefix "<setName>: " + named counter + drop on input and forward chains.
14
+ * IPv6 sets auto-append '6'.
15
+ */
12
16
  sets: string[];
17
+ /**
18
+ * Output IP set names (optional). Block by destination address.
19
+ * Rules: named counter + drop on output chain (no log).
20
+ * IPv6 sets auto-append '6'.
21
+ */
22
+ outSets?: string[];
23
+ /**
24
+ * Output port set names (optional). Block by tcp/udp destination port.
25
+ * Rules: single concatenated (proto . port) lookup + named counter + drop on output chain (no log).
26
+ * Port is added to BOTH IPv4 and IPv6 tables (ports are family-independent).
27
+ * IPv6 sets auto-append '6'.
28
+ */
29
+ outPortSets?: string[];
13
30
  }
14
31
 
15
32
  /** Options for adding a single address. */
16
33
  export interface AddAddressOptions {
17
34
  /** IPv4 or IPv6 address (e.g., "1.2.3.4" or "2001:db8::1"). */
18
35
  ip: string;
19
- /** Target set name (must match one from constructor's sets array). */
36
+ /** Target set name (must match one from constructor's sets or outSets). */
20
37
  set: string;
21
- /** Timeout in seconds. Omit for permanent ban. */
38
+ /** Timeout in seconds. Omit for permanent. */
22
39
  timeout?: number;
23
40
  }
24
41
 
@@ -26,7 +43,7 @@ export interface AddAddressOptions {
26
43
  export interface RemoveAddressOptions {
27
44
  /** IPv4 or IPv6 address to remove. */
28
45
  ip: string;
29
- /** Target set name (must match one from constructor's sets array). */
46
+ /** Target set name (must match one from constructor's sets or outSets). */
30
47
  set: string;
31
48
  }
32
49
 
@@ -34,9 +51,9 @@ export interface RemoveAddressOptions {
34
51
  export interface AddAddressesOptions {
35
52
  /** Array of IPv4/IPv6 addresses. */
36
53
  ips: string[];
37
- /** Target set name (must match one from constructor's sets array). */
54
+ /** Target set name (must match one from constructor's sets or outSets). */
38
55
  set: string;
39
- /** Timeout in seconds. Omit for permanent ban. */
56
+ /** Timeout in seconds. Omit for permanent. */
40
57
  timeout?: number;
41
58
  }
42
59
 
@@ -44,8 +61,52 @@ export interface AddAddressesOptions {
44
61
  export interface RemoveAddressesOptions {
45
62
  /** Array of IPv4/IPv6 addresses to remove. */
46
63
  ips: string[];
47
- /** Target set name (must match one from constructor's sets array). */
64
+ /** Target set name (must match one from constructor's sets or outSets). */
65
+ set: string;
66
+ }
67
+
68
+ /** Options for adding a single port. */
69
+ export interface AddPortOptions {
70
+ /** Port number (0-65535). */
71
+ port: number;
72
+ /** Target port set name (must match one from constructor's outPortSets). */
73
+ set: string;
74
+ /** Protocol: 'tcp', 'udp', or omit for both. Default: both. */
75
+ protocol?: 'tcp' | 'udp';
76
+ /** Timeout in seconds. Omit for permanent. */
77
+ timeout?: number;
78
+ }
79
+
80
+ /** Options for removing a single port. */
81
+ export interface RemovePortOptions {
82
+ /** Port number (0-65535). */
83
+ port: number;
84
+ /** Target port set name (must match one from constructor's outPortSets). */
85
+ set: string;
86
+ /** Protocol: 'tcp', 'udp', or omit for both. Default: both. */
87
+ protocol?: 'tcp' | 'udp';
88
+ }
89
+
90
+ /** Options for bulk adding ports. */
91
+ export interface AddPortsOptions {
92
+ /** Array of port numbers (0-65535). */
93
+ ports: number[];
94
+ /** Target port set name (must match one from constructor's outPortSets). */
48
95
  set: string;
96
+ /** Protocol: 'tcp', 'udp', or omit for both. Default: both. */
97
+ protocol?: 'tcp' | 'udp';
98
+ /** Timeout in seconds. Omit for permanent. */
99
+ timeout?: number;
100
+ }
101
+
102
+ /** Options for bulk removing ports. */
103
+ export interface RemovePortsOptions {
104
+ /** Array of port numbers (0-65535). */
105
+ ports: number[];
106
+ /** Target port set name (must match one from constructor's outPortSets). */
107
+ set: string;
108
+ /** Protocol: 'tcp', 'udp', or omit for both. Default: both. */
109
+ protocol?: 'tcp' | 'udp';
49
110
  }
50
111
 
51
112
  export class NftManager {
@@ -60,9 +121,17 @@ export class NftManager {
60
121
  constructor(options: NftManagerOptions);
61
122
 
62
123
  /**
63
- * Creates IPv4 and IPv6 tables with all configured sets and filter chains.
124
+ * Creates IPv4 and IPv6 tables with all configured sets, chains, named counters, and filter rules.
64
125
  * Idempotent — destroys existing tables first, then recreates.
65
126
  *
127
+ * Creates:
128
+ * - Named counter "processed" (global traffic counter per chain)
129
+ * - Named counter per set (blocked traffic counter)
130
+ * - Input chain with log + counter + drop rules (for sets)
131
+ * - Forward chain with log + counter + drop rules (for sets)
132
+ * - Output chain with counter + drop rules (for outSets and outPortSets, no log)
133
+ * - Per-element counters on all sets
134
+ *
66
135
  * @throws {Error} if nftables operation fails
67
136
  */
68
137
  createTable(): Promise<void>;
@@ -78,6 +147,7 @@ export class NftManager {
78
147
  /**
79
148
  * Adds an IP address to a set.
80
149
  * Auto-detects IPv4 vs IPv6 and routes to the correct table/set.
150
+ * Works with both input sets (sets) and output sets (outSets).
81
151
  *
82
152
  * @param options - Address, target set name, and optional timeout.
83
153
  * @throws {TypeError} if options or fields have wrong types
@@ -88,6 +158,7 @@ export class NftManager {
88
158
  /**
89
159
  * Removes an IP address from a set.
90
160
  * Idempotent — no error if IP is not in the set.
161
+ * Works with both input sets (sets) and output sets (outSets).
91
162
  *
92
163
  * @param options - Address and target set name.
93
164
  * @throws {TypeError} if options or fields have wrong types
@@ -115,4 +186,50 @@ export class NftManager {
115
186
  * @throws {Error} if any IP is invalid, set name is unknown, or nftables operation fails
116
187
  */
117
188
  removeAddresses(options: RemoveAddressesOptions): Promise<void>;
189
+
190
+ /**
191
+ * Adds a port to an output port set.
192
+ * Port is added to BOTH IPv4 and IPv6 tables (ports are family-independent).
193
+ * When protocol is omitted, two elements are added: tcp + udp.
194
+ * When protocol is 'tcp' or 'udp', one element with that protocol is added.
195
+ *
196
+ * @param options - Port number, target set name, and optional timeout.
197
+ * @throws {TypeError} if options or fields have wrong types
198
+ * @throws {Error} if port is invalid, set name is unknown, or nftables operation fails
199
+ */
200
+ addPort(options: AddPortOptions): Promise<void>;
201
+
202
+ /**
203
+ * Removes a port from an output port set.
204
+ * Idempotent — no error if port is not in the set.
205
+ * Port is removed from BOTH IPv4 and IPv6 tables.
206
+ *
207
+ * @param options - Port number and target set name.
208
+ * @throws {TypeError} if options or fields have wrong types
209
+ * @throws {Error} if port is invalid, set name is unknown, or nftables operation fails
210
+ */
211
+ removePort(options: RemovePortOptions): Promise<void>;
212
+
213
+ /**
214
+ * Adds multiple ports to an output port set in bulk.
215
+ * Ports are added to BOTH IPv4 and IPv6 tables.
216
+ * Empty arrays are a no-op.
217
+ *
218
+ * @param options - Array of port numbers, target set name, and optional timeout.
219
+ * @throws {TypeError} if options or fields have wrong types
220
+ * @throws {Error} if any port is invalid, set name is unknown, or nftables operation fails
221
+ */
222
+ addPorts(options: AddPortsOptions): Promise<void>;
223
+
224
+ /**
225
+ * Removes multiple ports from an output port set in bulk.
226
+ * Idempotent — no error if ports are not in the set.
227
+ * Ports are removed from BOTH IPv4 and IPv6 tables.
228
+ * Empty arrays are a no-op.
229
+ *
230
+ * @param options - Array of port numbers and target set name.
231
+ * @throws {TypeError} if options or fields have wrong types
232
+ * @throws {Error} if any port is invalid, set name is unknown, or nftables operation fails
233
+ */
234
+ removePorts(options: RemovePortsOptions): Promise<void>;
118
235
  }
package/lib/index.js CHANGED
@@ -6,7 +6,7 @@ let binding;
6
6
  try {
7
7
  binding = require('node-gyp-build')(path.join(__dirname, '..'));
8
8
  } catch (e) {
9
- // On non-Linux platforms, the native addon may fail to load
9
+ if (process.platform === 'linux') throw e;
10
10
  binding = null;
11
11
  }
12
12
 
@@ -16,7 +16,7 @@ if (binding) {
16
16
  class NftManager {
17
17
  constructor() {
18
18
  throw new Error(
19
- 'remnawave-nft only works on Linux with CAP_NET_ADMIN. Native binding failed to load.'
19
+ 'nftables-napi only works on Linux with CAP_NET_ADMIN. Native binding failed to load.'
20
20
  );
21
21
  }
22
22
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nftables-napi",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Native Node.js binding for nftables via libnftnl+libmnl — nftables firewall management",
5
5
  "author": {
6
6
  "name": "kastov",
@@ -46,8 +46,7 @@
46
46
  ],
47
47
  "gypfile": true,
48
48
  "engines": {
49
- "node": ">=24.0.0",
50
- "os": "linux"
49
+ "node": ">=24.0.0"
51
50
  },
52
51
  "scripts": {
53
52
  "install": "node-gyp-build || true",
@@ -57,10 +56,10 @@
57
56
  "prebuild:all": "./prebuild-all.sh"
58
57
  },
59
58
  "dependencies": {
60
- "node-gyp-build": "^4.8.4",
61
- "node-addon-api": "^8.5.0"
59
+ "node-gyp-build": "^4.8.4"
62
60
  },
63
61
  "devDependencies": {
62
+ "node-addon-api": "^8.5.0",
64
63
  "node-gyp": "^12.2.0",
65
64
  "prebuildify": "^6.0.1",
66
65
  "@types/node": ">= 24 < 25"
@@ -23,6 +23,10 @@ inline constexpr uint32_t IPV6_SRC_OFFSET = 8;
23
23
  inline constexpr uint32_t IPV4_ADDR_LEN = 4;
24
24
  inline constexpr uint32_t IPV6_ADDR_LEN = 16;
25
25
 
26
+ // Destination address offsets
27
+ inline constexpr uint32_t IPV4_DST_OFFSET = 16;
28
+ inline constexpr uint32_t IPV6_DST_OFFSET = 24;
29
+
26
30
  // Chain priority
27
31
  inline constexpr int32_t CHAIN_PRIORITY = -10;
28
32
 
@@ -36,4 +40,25 @@ inline constexpr uint32_t SEQ_BLOCK_SIZE = 256;
36
40
  // Number of pages for default batch buffer (pagesize * 32 ≈ 128KB)
37
41
  inline constexpr uint32_t DEFAULT_BUF_PAGES = 32;
38
42
 
43
+ // Output chain
44
+ inline constexpr const char* CHAIN_OUTPUT = "output";
45
+
46
+ // Transport header dport offset (bytes from transport header base)
47
+ inline constexpr uint32_t TRANSPORT_DPORT_OFFSET = 2;
48
+ inline constexpr uint32_t TRANSPORT_DPORT_LEN = 2;
49
+
50
+ // Concatenated type: concat_subtype_add(inet_proto, inet_service)
51
+ // nft CLI builds concat types with LAST subtype in low bits:
52
+ // concat_subtype_add(type, subtype) = type << TYPE_BITS | subtype
53
+ // For (inet_proto=12 . inet_service=13): 12 << 6 | 13 = 781
54
+ inline constexpr uint32_t DATATYPE_PROTO_SERVICE = (12 << 6 | 13);
55
+ inline constexpr uint32_t PROTO_SERVICE_KEY_LEN = 8;
56
+
57
+ // L4 protocol numbers
58
+ inline constexpr uint8_t PROTO_TCP = 6;
59
+ inline constexpr uint8_t PROTO_UDP = 17;
60
+
61
+ // Named counter
62
+ inline constexpr const char* COUNTER_NAME = "processed";
63
+
39
64
  } // namespace nft
@@ -5,25 +5,36 @@
5
5
 
6
6
  namespace nft {
7
7
 
8
+ enum class SetKind { InIP, OutIP, OutPort };
9
+
8
10
  struct SetDef {
9
- std::string name; // user-facing key = nftables IPv4 set name
10
- std::string name_v6; // name + "6"
11
- std::string log_prefix; // name + ": "
11
+ std::string name;
12
+ std::string name_v6;
13
+ std::string log_prefix; // non-empty for InIP only
14
+ SetKind kind;
12
15
  };
13
16
 
14
17
  struct NftConfig {
15
18
  std::string table_v4;
16
19
  std::string table_v6;
17
- std::vector<SetDef> sets;
20
+ std::vector<SetDef> sets; // all sets: InIP + OutIP + OutPort
18
21
 
19
22
  static NftConfig from_names(const std::string& table_name,
20
- const std::vector<std::string>& set_names) {
23
+ const std::vector<std::string>& in_sets,
24
+ const std::vector<std::string>& out_sets,
25
+ const std::vector<std::string>& out_port_sets) {
21
26
  NftConfig cfg;
22
27
  cfg.table_v4 = table_name;
23
28
  cfg.table_v6 = table_name + "6";
24
- cfg.sets.reserve(set_names.size());
25
- for (const auto& n : set_names) {
26
- cfg.sets.push_back({n, n + "6", n + ": "});
29
+ cfg.sets.reserve(in_sets.size() + out_sets.size() + out_port_sets.size());
30
+ for (const auto& n : in_sets) {
31
+ cfg.sets.push_back({n, n + "6", n + ": ", SetKind::InIP});
32
+ }
33
+ for (const auto& n : out_sets) {
34
+ cfg.sets.push_back({n, n + "6", "", SetKind::OutIP});
35
+ }
36
+ for (const auto& n : out_port_sets) {
37
+ cfg.sets.push_back({n, n + "6", "", SetKind::OutPort});
27
38
  }
28
39
  return cfg;
29
40
  }
@@ -24,6 +24,7 @@ extern "C" {
24
24
  #include <libnftnl/chain.h>
25
25
  #include <libnftnl/set.h>
26
26
  #include <libnftnl/rule.h>
27
+ #include <libnftnl/object.h>
27
28
  }
28
29
 
29
30
  #include <memory>
@@ -37,7 +38,7 @@ struct NftnlTableDeleter {
37
38
  };
38
39
  using UniqueTable = std::unique_ptr<struct nftnl_table, NftnlTableDeleter>;
39
40
 
40
- inline UniqueTable make_table() { return UniqueTable(nftnl_table_alloc()); }
41
+ [[nodiscard]] inline UniqueTable make_table() { return UniqueTable(nftnl_table_alloc()); }
41
42
 
42
43
  // --- Chain -------------------------------------------------------------------
43
44
 
@@ -46,7 +47,7 @@ struct NftnlChainDeleter {
46
47
  };
47
48
  using UniqueChain = std::unique_ptr<struct nftnl_chain, NftnlChainDeleter>;
48
49
 
49
- inline UniqueChain make_chain() { return UniqueChain(nftnl_chain_alloc()); }
50
+ [[nodiscard]] inline UniqueChain make_chain() { return UniqueChain(nftnl_chain_alloc()); }
50
51
 
51
52
  // --- Set ---------------------------------------------------------------------
52
53
 
@@ -55,7 +56,7 @@ struct NftnlSetDeleter {
55
56
  };
56
57
  using UniqueSet = std::unique_ptr<struct nftnl_set, NftnlSetDeleter>;
57
58
 
58
- inline UniqueSet make_set() { return UniqueSet(nftnl_set_alloc()); }
59
+ [[nodiscard]] inline UniqueSet make_set() { return UniqueSet(nftnl_set_alloc()); }
59
60
 
60
61
  // --- Rule --------------------------------------------------------------------
61
62
 
@@ -64,6 +65,15 @@ struct NftnlRuleDeleter {
64
65
  };
65
66
  using UniqueRule = std::unique_ptr<struct nftnl_rule, NftnlRuleDeleter>;
66
67
 
67
- inline UniqueRule make_rule() { return UniqueRule(nftnl_rule_alloc()); }
68
+ [[nodiscard]] inline UniqueRule make_rule() { return UniqueRule(nftnl_rule_alloc()); }
69
+
70
+ // --- Object (stateful counter/quota) ----------------------------------------
71
+
72
+ struct NftnlObjDeleter {
73
+ void operator()(struct nftnl_obj* o) const noexcept { nftnl_obj_free(o); }
74
+ };
75
+ using UniqueObj = std::unique_ptr<struct nftnl_obj, NftnlObjDeleter>;
76
+
77
+ [[nodiscard]] inline UniqueObj make_obj() { return UniqueObj(nftnl_obj_alloc()); }
68
78
 
69
79
  } // namespace nft
@@ -24,10 +24,10 @@ public:
24
24
  NlBatch(NlBatch&&) = delete;
25
25
  NlBatch& operator=(NlBatch&&) = delete;
26
26
 
27
- bool is_valid() const;
28
- struct nlmsghdr* add_msg(uint16_t type, uint16_t family, uint16_t flags);
29
- bool advance();
30
- NlResult execute(NlSocket& sock, bool ignore_enoent = false);
27
+ [[nodiscard]] bool is_valid() const;
28
+ [[nodiscard]] struct nlmsghdr* add_msg(uint16_t type, uint16_t family, uint16_t flags);
29
+ [[nodiscard]] bool advance();
30
+ [[nodiscard]] NlResult execute(NlSocket& sock, bool ignore_enoent = false);
31
31
 
32
32
  private:
33
33
  std::unique_ptr<char[]> buf_;
@@ -2,7 +2,7 @@
2
2
 
3
3
  #include <string>
4
4
 
5
- struct NlResult {
5
+ struct [[nodiscard]] NlResult {
6
6
  bool success;
7
7
  std::string error;
8
8
  };