nftables-napi 0.1.0 → 0.1.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/binding.gyp +3 -1
- package/lib/index.d.ts +126 -9
- package/lib/index.js +2 -2
- package/package.json +4 -5
- package/prebuilds/linux-arm64/nftables-napi.node +0 -0
- package/prebuilds/linux-x64/nftables-napi.node +0 -0
- package/src/netlink/constants.h +25 -0
- package/src/netlink/nft_config.h +19 -8
- package/src/netlink/nftnl_raii.h +14 -4
- package/src/netlink/nl_batch.h +4 -4
- package/src/netlink/nl_result.h +1 -1
- package/src/netlink/nl_socket.cpp +20 -16
- package/src/netlink/nl_socket.h +2 -2
- package/src/netlink/operation.h +1 -1
- package/src/netlink/set_ops.cpp +88 -0
- package/src/netlink/set_ops.h +31 -0
- package/src/netlink/table_ops.cpp +196 -20
- package/src/nft_manager.cpp +364 -46
- package/src/nft_manager.h +5 -0
- package/src/validation.cpp +12 -0
- package/src/validation.h +10 -1
package/binding.gyp
CHANGED
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.
|
|
7
|
+
/** Constructor options. */
|
|
8
8
|
export interface NftManagerOptions {
|
|
9
9
|
/** Base table name. IPv6 table auto-appends '6'. */
|
|
10
10
|
tableName: string;
|
|
11
|
-
/**
|
|
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
|
|
36
|
+
/** Target set name (must match one from constructor's sets or outSets). */
|
|
20
37
|
set: string;
|
|
21
|
-
/** Timeout in seconds. Omit for permanent
|
|
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
|
|
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
|
|
54
|
+
/** Target set name (must match one from constructor's sets or outSets). */
|
|
38
55
|
set: string;
|
|
39
|
-
/** Timeout in seconds. Omit for permanent
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
'
|
|
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.
|
|
3
|
+
"version": "0.1.1",
|
|
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"
|
|
Binary file
|
|
Binary file
|
package/src/netlink/constants.h
CHANGED
|
@@ -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
|
package/src/netlink/nft_config.h
CHANGED
|
@@ -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;
|
|
10
|
-
std::string name_v6;
|
|
11
|
-
std::string log_prefix; //
|
|
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>&
|
|
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(
|
|
25
|
-
for (const auto& n :
|
|
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
|
}
|
package/src/netlink/nftnl_raii.h
CHANGED
|
@@ -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
|
package/src/netlink/nl_batch.h
CHANGED
|
@@ -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_;
|
package/src/netlink/nl_result.h
CHANGED
|
@@ -6,6 +6,7 @@ extern "C" {
|
|
|
6
6
|
|
|
7
7
|
#include <cerrno>
|
|
8
8
|
#include <cstring>
|
|
9
|
+
#include <sys/select.h>
|
|
9
10
|
#include <sys/socket.h>
|
|
10
11
|
|
|
11
12
|
static constexpr size_t RECV_BUF_SIZE = 16384;
|
|
@@ -84,31 +85,34 @@ NlResult NlSocket::send_batch(struct mnl_nlmsg_batch* batch, bool ignore_enoent)
|
|
|
84
85
|
|
|
85
86
|
BatchRecvCtx ctx{ignore_enoent, false, 0};
|
|
86
87
|
|
|
87
|
-
// Custom control callback array: override only NLMSG_ERROR (index 2).
|
|
88
|
-
// Array size = NLMSG_ERROR + 1 = 3, so NLMSG_DONE (3) and others
|
|
89
|
-
// fall through to default mnl handling.
|
|
90
88
|
mnl_cb_t cb_ctl[NLMSG_ERROR + 1] = {};
|
|
91
89
|
cb_ctl[NLMSG_ERROR] = batch_error_cb;
|
|
92
90
|
|
|
93
91
|
int fd = mnl_socket_get_fd(nl_);
|
|
94
92
|
char recv_buf[RECV_BUF_SIZE];
|
|
95
93
|
|
|
96
|
-
//
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
94
|
+
// Match nft CLI pattern: select() with zero timeout to check for data,
|
|
95
|
+
// then recv+process in a loop. Continue on errors to collect all ACKs.
|
|
96
|
+
fd_set readfds;
|
|
97
|
+
struct timeval tv;
|
|
98
|
+
|
|
99
|
+
for (;;) {
|
|
100
|
+
FD_ZERO(&readfds);
|
|
101
|
+
FD_SET(fd, &readfds);
|
|
102
|
+
tv = {0, 0};
|
|
103
|
+
|
|
104
|
+
int sel = select(fd + 1, &readfds, nullptr, nullptr, &tv);
|
|
105
|
+
if (sel <= 0)
|
|
104
106
|
break;
|
|
105
107
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
108
|
+
ssize_t ret = mnl_socket_recvfrom(nl_, recv_buf, sizeof(recv_buf));
|
|
109
|
+
if (ret < 0)
|
|
110
|
+
return {false, std::string("mnl_socket_recvfrom: ") + strerror(errno)};
|
|
109
111
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
+
mnl_cb_run2(recv_buf, static_cast<size_t>(ret), 0, portid_,
|
|
113
|
+
nullptr, &ctx, cb_ctl, NLMSG_ERROR + 1);
|
|
114
|
+
// Continue on error — collect all acknowledgments
|
|
115
|
+
}
|
|
112
116
|
|
|
113
117
|
if (ctx.has_error)
|
|
114
118
|
return {false, std::string("batch error: ") + strerror(ctx.error_code)};
|
package/src/netlink/nl_socket.h
CHANGED
|
@@ -18,9 +18,9 @@ public:
|
|
|
18
18
|
NlSocket(NlSocket&&) = delete;
|
|
19
19
|
NlSocket& operator=(NlSocket&&) = delete;
|
|
20
20
|
|
|
21
|
-
bool is_valid() const;
|
|
21
|
+
[[nodiscard]] bool is_valid() const;
|
|
22
22
|
|
|
23
|
-
NlResult send_batch(struct mnl_nlmsg_batch* batch, bool ignore_enoent = false);
|
|
23
|
+
[[nodiscard]] NlResult send_batch(struct mnl_nlmsg_batch* batch, bool ignore_enoent = false);
|
|
24
24
|
|
|
25
25
|
private:
|
|
26
26
|
struct mnl_socket* nl_;
|
package/src/netlink/operation.h
CHANGED
package/src/netlink/set_ops.cpp
CHANGED
|
@@ -15,6 +15,7 @@ extern "C" {
|
|
|
15
15
|
|
|
16
16
|
static_assert(nft::FAMILY_IPV4 == NFPROTO_IPV4, "nft::FAMILY_IPV4 must match NFPROTO_IPV4");
|
|
17
17
|
static_assert(nft::FAMILY_IPV6 == NFPROTO_IPV6, "nft::FAMILY_IPV6 must match NFPROTO_IPV6");
|
|
18
|
+
static_assert(nft::PROTO_SERVICE_KEY_LEN == 8, "concatenated proto.service key must be 8 bytes");
|
|
18
19
|
|
|
19
20
|
enum class SetElemAction { Add, Del };
|
|
20
21
|
|
|
@@ -108,3 +109,90 @@ BulkDelSetElemOp::BulkDelSetElemOp(std::vector<ParsedAddr> addrs,
|
|
|
108
109
|
NlResult BulkDelSetElemOp::execute(NlSocket& sock) {
|
|
109
110
|
return bulk_set_elem_op(addrs_, sock, SetElemAction::Del, *cfg_, set_idx_);
|
|
110
111
|
}
|
|
112
|
+
|
|
113
|
+
static NlResult bulk_port_elem_op(
|
|
114
|
+
const std::vector<PortElem>& elems,
|
|
115
|
+
NlSocket& sock,
|
|
116
|
+
SetElemAction action,
|
|
117
|
+
const nft::NftConfig& cfg,
|
|
118
|
+
size_t set_idx,
|
|
119
|
+
uint64_t timeout_ms = 0)
|
|
120
|
+
{
|
|
121
|
+
const uint16_t msg_type = (action == SetElemAction::Add)
|
|
122
|
+
? NFT_MSG_NEWSETELEM : NFT_MSG_DELSETELEM;
|
|
123
|
+
const uint16_t flags = (action == SetElemAction::Add)
|
|
124
|
+
? (NLM_F_CREATE | NLM_F_ACK) : NLM_F_ACK;
|
|
125
|
+
const bool ignore_enoent = (action == SetElemAction::Del);
|
|
126
|
+
|
|
127
|
+
const auto& sd = cfg.sets[set_idx];
|
|
128
|
+
|
|
129
|
+
struct FamilyInfo {
|
|
130
|
+
uint32_t family;
|
|
131
|
+
const char* table;
|
|
132
|
+
const char* set_name;
|
|
133
|
+
};
|
|
134
|
+
FamilyInfo families[] = {
|
|
135
|
+
{NFPROTO_IPV4, cfg.table_v4.c_str(), sd.name.c_str()},
|
|
136
|
+
{NFPROTO_IPV6, cfg.table_v6.c_str(), sd.name_v6.c_str()},
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
for (const auto& fi : families) {
|
|
140
|
+
for (size_t offset = 0; offset < elems.size(); offset += nft::BULK_CHUNK_SIZE) {
|
|
141
|
+
size_t end = std::min(offset + static_cast<size_t>(nft::BULK_CHUNK_SIZE), elems.size());
|
|
142
|
+
|
|
143
|
+
auto s = nft::make_set();
|
|
144
|
+
if (!s) return {false, "nftnl_set_alloc failed"};
|
|
145
|
+
|
|
146
|
+
nftnl_set_set_str(s.get(), NFTNL_SET_TABLE, fi.table);
|
|
147
|
+
nftnl_set_set_str(s.get(), NFTNL_SET_NAME, fi.set_name);
|
|
148
|
+
nftnl_set_set_u32(s.get(), NFTNL_SET_FAMILY, fi.family);
|
|
149
|
+
nftnl_set_set_u32(s.get(), NFTNL_SET_KEY_TYPE, nft::DATATYPE_PROTO_SERVICE);
|
|
150
|
+
nftnl_set_set_u32(s.get(), NFTNL_SET_KEY_LEN, nft::PROTO_SERVICE_KEY_LEN);
|
|
151
|
+
|
|
152
|
+
for (size_t i = offset; i < end; ++i) {
|
|
153
|
+
auto* e = nftnl_set_elem_alloc();
|
|
154
|
+
if (!e) return {false, "nftnl_set_elem_alloc failed"};
|
|
155
|
+
// 8-byte concatenated key: [proto][pad:3][port_hi][port_lo][pad:2]
|
|
156
|
+
uint8_t key[8] = {};
|
|
157
|
+
key[0] = elems[i].proto;
|
|
158
|
+
key[4] = static_cast<uint8_t>(elems[i].port >> 8);
|
|
159
|
+
key[5] = static_cast<uint8_t>(elems[i].port & 0xFF);
|
|
160
|
+
nftnl_set_elem_set(e, NFTNL_SET_ELEM_KEY, key, sizeof(key));
|
|
161
|
+
if (timeout_ms > 0) {
|
|
162
|
+
nftnl_set_elem_set_u64(e, NFTNL_SET_ELEM_TIMEOUT, timeout_ms);
|
|
163
|
+
}
|
|
164
|
+
nftnl_set_elem_add(s.get(), e);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
NlBatch batch;
|
|
168
|
+
if (!batch.is_valid()) return {false, "failed to allocate batch"};
|
|
169
|
+
|
|
170
|
+
auto* nlh = batch.add_msg(msg_type, fi.family, flags);
|
|
171
|
+
if (!nlh) return {false, "failed to add message to batch"};
|
|
172
|
+
nftnl_set_elems_nlmsg_build_payload(nlh, s.get());
|
|
173
|
+
|
|
174
|
+
if (!batch.advance()) return {false, "batch buffer full"};
|
|
175
|
+
|
|
176
|
+
NlResult res = batch.execute(sock, ignore_enoent);
|
|
177
|
+
if (!res.success) return res;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return {true, ""};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
BulkAddPortElemOp::BulkAddPortElemOp(std::vector<PortElem> elems, uint64_t timeout_ms,
|
|
184
|
+
std::shared_ptr<const nft::NftConfig> config, size_t set_idx)
|
|
185
|
+
: elems_(std::move(elems)), timeout_ms_(timeout_ms),
|
|
186
|
+
cfg_(std::move(config)), set_idx_(set_idx) {}
|
|
187
|
+
|
|
188
|
+
NlResult BulkAddPortElemOp::execute(NlSocket& sock) {
|
|
189
|
+
return bulk_port_elem_op(elems_, sock, SetElemAction::Add, *cfg_, set_idx_, timeout_ms_);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
BulkDelPortElemOp::BulkDelPortElemOp(std::vector<PortElem> elems,
|
|
193
|
+
std::shared_ptr<const nft::NftConfig> config, size_t set_idx)
|
|
194
|
+
: elems_(std::move(elems)), cfg_(std::move(config)), set_idx_(set_idx) {}
|
|
195
|
+
|
|
196
|
+
NlResult BulkDelPortElemOp::execute(NlSocket& sock) {
|
|
197
|
+
return bulk_port_elem_op(elems_, sock, SetElemAction::Del, *cfg_, set_idx_);
|
|
198
|
+
}
|
package/src/netlink/set_ops.h
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
#include "operation.h"
|
|
4
4
|
#include "nft_config.h"
|
|
5
5
|
#include "parsed_addr.h"
|
|
6
|
+
#include <cstdint>
|
|
6
7
|
#include <memory>
|
|
7
8
|
#include <vector>
|
|
8
9
|
|
|
@@ -30,3 +31,33 @@ private:
|
|
|
30
31
|
std::shared_ptr<const nft::NftConfig> cfg_;
|
|
31
32
|
size_t set_idx_;
|
|
32
33
|
};
|
|
34
|
+
|
|
35
|
+
struct PortElem {
|
|
36
|
+
uint8_t proto; // nft::PROTO_TCP or nft::PROTO_UDP
|
|
37
|
+
uint16_t port;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
class BulkAddPortElemOp final : public NlOperation {
|
|
41
|
+
public:
|
|
42
|
+
BulkAddPortElemOp(std::vector<PortElem> elems, uint64_t timeout_ms,
|
|
43
|
+
std::shared_ptr<const nft::NftConfig> config, size_t set_idx);
|
|
44
|
+
NlResult execute(NlSocket& sock) override;
|
|
45
|
+
|
|
46
|
+
private:
|
|
47
|
+
std::vector<PortElem> elems_;
|
|
48
|
+
uint64_t timeout_ms_;
|
|
49
|
+
std::shared_ptr<const nft::NftConfig> cfg_;
|
|
50
|
+
size_t set_idx_;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
class BulkDelPortElemOp final : public NlOperation {
|
|
54
|
+
public:
|
|
55
|
+
BulkDelPortElemOp(std::vector<PortElem> elems,
|
|
56
|
+
std::shared_ptr<const nft::NftConfig> config, size_t set_idx);
|
|
57
|
+
NlResult execute(NlSocket& sock) override;
|
|
58
|
+
|
|
59
|
+
private:
|
|
60
|
+
std::vector<PortElem> elems_;
|
|
61
|
+
std::shared_ptr<const nft::NftConfig> cfg_;
|
|
62
|
+
size_t set_idx_;
|
|
63
|
+
};
|