nftables-napi 0.0.2 → 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/README.md +8 -12
- package/binding.gyp +3 -1
- package/lib/index.d.ts +149 -34
- 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 +26 -26
- 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 +103 -18
- package/src/netlink/set_ops.h +35 -4
- package/src/netlink/table_ops.cpp +203 -46
- package/src/nft_manager.cpp +413 -62
- package/src/nft_manager.h +5 -0
- package/src/validation.cpp +12 -0
- package/src/validation.h +10 -1
package/src/nft_manager.cpp
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
#include "netlink/parsed_addr.h"
|
|
5
5
|
#include "netlink/table_ops.h"
|
|
6
6
|
#include "netlink/set_ops.h"
|
|
7
|
+
#include "netlink/constants.h"
|
|
7
8
|
|
|
8
9
|
#include <cmath>
|
|
9
10
|
#include <cstring>
|
|
@@ -46,23 +47,41 @@ static std::vector<ParsedAddr> parse_ip_array(Napi::Env env, Napi::Array arr) {
|
|
|
46
47
|
return addrs;
|
|
47
48
|
}
|
|
48
49
|
|
|
49
|
-
//
|
|
50
|
-
|
|
50
|
+
// Parse set name, filtering by allowed SetKind values.
|
|
51
|
+
// allow_ip=true matches InIP and OutIP; allow_port=true matches OutPort.
|
|
52
|
+
static std::optional<size_t> parse_set_name(Napi::Env env, Napi::Object opts,
|
|
53
|
+
const char* method_name,
|
|
54
|
+
const nft::NftConfig& cfg,
|
|
55
|
+
bool allow_ip, bool allow_port) {
|
|
51
56
|
if (!opts.Has("set") || !opts.Get("set").IsString()) {
|
|
52
|
-
std::string msg = std::string(method_name) + ": 'set' is required and must be a string
|
|
57
|
+
std::string msg = std::string(method_name) + ": 'set' is required and must be a string";
|
|
53
58
|
Napi::TypeError::New(env, msg).ThrowAsJavaScriptException();
|
|
54
59
|
return std::nullopt;
|
|
55
60
|
}
|
|
56
61
|
std::string set_str = opts.Get("set").As<Napi::String>().Utf8Value();
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
62
|
+
|
|
63
|
+
for (size_t i = 0; i < cfg.sets.size(); ++i) {
|
|
64
|
+
if (cfg.sets[i].name != set_str) continue;
|
|
65
|
+
bool is_ip = (cfg.sets[i].kind == nft::SetKind::InIP || cfg.sets[i].kind == nft::SetKind::OutIP);
|
|
66
|
+
bool is_port = (cfg.sets[i].kind == nft::SetKind::OutPort);
|
|
67
|
+
if ((allow_ip && is_ip) || (allow_port && is_port)) return i;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Build valid names for error message
|
|
71
|
+
std::string valid;
|
|
72
|
+
for (size_t i = 0; i < cfg.sets.size(); ++i) {
|
|
73
|
+
bool is_ip = (cfg.sets[i].kind == nft::SetKind::InIP || cfg.sets[i].kind == nft::SetKind::OutIP);
|
|
74
|
+
bool is_port = (cfg.sets[i].kind == nft::SetKind::OutPort);
|
|
75
|
+
if (!((allow_ip && is_ip) || (allow_port && is_port))) continue;
|
|
76
|
+
if (!valid.empty()) valid += ", ";
|
|
77
|
+
valid += "'" + cfg.sets[i].name + "'";
|
|
78
|
+
}
|
|
79
|
+
std::string msg = std::string(method_name) + ": 'set' must be one of: " + valid;
|
|
60
80
|
Napi::Error::New(env, msg).ThrowAsJavaScriptException();
|
|
61
81
|
return std::nullopt;
|
|
62
82
|
}
|
|
63
83
|
|
|
64
84
|
// Returns timeout in ms (0 = permanent/no timeout). Sets JS exception on error.
|
|
65
|
-
// Returns std::nullopt on error.
|
|
66
85
|
static std::optional<uint64_t> parse_timeout(Napi::Env env, Napi::Object opts, const char* method_name) {
|
|
67
86
|
if (!opts.Has("timeout") || opts.Get("timeout").IsUndefined() || opts.Get("timeout").IsNull()) {
|
|
68
87
|
return uint64_t{0}; // permanent
|
|
@@ -82,6 +101,129 @@ static std::optional<uint64_t> parse_timeout(Napi::Env env, Napi::Object opts, c
|
|
|
82
101
|
return static_cast<uint64_t>(timeout_sec * 1000.0);
|
|
83
102
|
}
|
|
84
103
|
|
|
104
|
+
// Parse a single port from opts.port
|
|
105
|
+
static std::optional<uint16_t> parse_port_value(Napi::Env env, Napi::Object opts,
|
|
106
|
+
const char* method_name) {
|
|
107
|
+
if (!opts.Has("port") || !opts.Get("port").IsNumber()) {
|
|
108
|
+
std::string msg = std::string(method_name) + ": 'port' is required and must be a number";
|
|
109
|
+
Napi::TypeError::New(env, msg).ThrowAsJavaScriptException();
|
|
110
|
+
return std::nullopt;
|
|
111
|
+
}
|
|
112
|
+
double val = opts.Get("port").As<Napi::Number>().DoubleValue();
|
|
113
|
+
PortVal pv = parse_port(val);
|
|
114
|
+
if (!pv.valid) {
|
|
115
|
+
Napi::Error::New(env, std::string(method_name) + ": 'port' must be an integer 0-65535")
|
|
116
|
+
.ThrowAsJavaScriptException();
|
|
117
|
+
return std::nullopt;
|
|
118
|
+
}
|
|
119
|
+
return pv.port;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Parse array of ports from opts.ports
|
|
123
|
+
static std::vector<uint16_t> parse_port_array(Napi::Env env, Napi::Array arr,
|
|
124
|
+
const char* method_name) {
|
|
125
|
+
std::vector<uint16_t> ports;
|
|
126
|
+
ports.reserve(arr.Length());
|
|
127
|
+
|
|
128
|
+
for (uint32_t i = 0; i < arr.Length(); ++i) {
|
|
129
|
+
Napi::Value val = arr[i];
|
|
130
|
+
if (!val.IsNumber()) {
|
|
131
|
+
Napi::TypeError::New(env, std::string(method_name) + ": each port must be a number")
|
|
132
|
+
.ThrowAsJavaScriptException();
|
|
133
|
+
return {};
|
|
134
|
+
}
|
|
135
|
+
double d = val.As<Napi::Number>().DoubleValue();
|
|
136
|
+
PortVal pv = parse_port(d);
|
|
137
|
+
if (!pv.valid) {
|
|
138
|
+
Napi::Error::New(env, std::string(method_name) +
|
|
139
|
+
": invalid port at index " + std::to_string(i))
|
|
140
|
+
.ThrowAsJavaScriptException();
|
|
141
|
+
return {};
|
|
142
|
+
}
|
|
143
|
+
ports.push_back(pv.port);
|
|
144
|
+
}
|
|
145
|
+
return ports;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Parse optional protocol: 'tcp', 'udp', or absent (both).
|
|
149
|
+
// Returns 0 for both, PROTO_TCP for tcp, PROTO_UDP for udp.
|
|
150
|
+
// Returns 255 on error (after throwing JS exception).
|
|
151
|
+
static uint8_t parse_protocol(Napi::Env env, Napi::Object opts, const char* method_name) {
|
|
152
|
+
if (!opts.Has("protocol") || opts.Get("protocol").IsUndefined() || opts.Get("protocol").IsNull()) {
|
|
153
|
+
return 0; // both
|
|
154
|
+
}
|
|
155
|
+
Napi::Value pv = opts.Get("protocol");
|
|
156
|
+
if (!pv.IsString()) {
|
|
157
|
+
std::string msg = std::string(method_name) + ": 'protocol' must be 'tcp' or 'udp'";
|
|
158
|
+
Napi::TypeError::New(env, msg).ThrowAsJavaScriptException();
|
|
159
|
+
return 255;
|
|
160
|
+
}
|
|
161
|
+
std::string proto = pv.As<Napi::String>().Utf8Value();
|
|
162
|
+
if (proto == "tcp") return nft::PROTO_TCP;
|
|
163
|
+
if (proto == "udp") return nft::PROTO_UDP;
|
|
164
|
+
std::string msg = std::string(method_name) + ": 'protocol' must be 'tcp' or 'udp'";
|
|
165
|
+
Napi::Error::New(env, msg).ThrowAsJavaScriptException();
|
|
166
|
+
return 255;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
static std::vector<PortElem> make_port_elems(uint16_t port, uint8_t proto) {
|
|
170
|
+
if (proto == 0) {
|
|
171
|
+
return {{nft::PROTO_TCP, port}, {nft::PROTO_UDP, port}};
|
|
172
|
+
}
|
|
173
|
+
return {{proto, port}};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
static std::vector<PortElem> make_port_elems_bulk(const std::vector<uint16_t>& ports, uint8_t proto) {
|
|
177
|
+
std::vector<PortElem> elems;
|
|
178
|
+
if (proto == 0) {
|
|
179
|
+
elems.reserve(ports.size() * 2);
|
|
180
|
+
for (uint16_t p : ports) {
|
|
181
|
+
elems.push_back({nft::PROTO_TCP, p});
|
|
182
|
+
elems.push_back({nft::PROTO_UDP, p});
|
|
183
|
+
}
|
|
184
|
+
} else {
|
|
185
|
+
elems.reserve(ports.size());
|
|
186
|
+
for (uint16_t p : ports) {
|
|
187
|
+
elems.push_back({proto, p});
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return elems;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Helper: parse optional string array from opts, returns empty vector if not present
|
|
194
|
+
static std::vector<std::string> parse_optional_string_array(Napi::Env env, Napi::Object opts,
|
|
195
|
+
const char* field_name,
|
|
196
|
+
const char* context) {
|
|
197
|
+
if (!opts.Has(field_name) || opts.Get(field_name).IsUndefined() || opts.Get(field_name).IsNull()) {
|
|
198
|
+
return {};
|
|
199
|
+
}
|
|
200
|
+
Napi::Value val = opts.Get(field_name);
|
|
201
|
+
if (!val.IsArray()) {
|
|
202
|
+
std::string msg = std::string(context) + ": '" + field_name + "' must be an array of strings";
|
|
203
|
+
Napi::TypeError::New(env, msg).ThrowAsJavaScriptException();
|
|
204
|
+
return {};
|
|
205
|
+
}
|
|
206
|
+
Napi::Array arr = val.As<Napi::Array>();
|
|
207
|
+
std::vector<std::string> result;
|
|
208
|
+
result.reserve(arr.Length());
|
|
209
|
+
for (uint32_t i = 0; i < arr.Length(); ++i) {
|
|
210
|
+
Napi::Value v = arr[i];
|
|
211
|
+
if (!v.IsString()) {
|
|
212
|
+
std::string msg = std::string(context) + ": '" + field_name + "[" + std::to_string(i) + "]' must be a string";
|
|
213
|
+
Napi::TypeError::New(env, msg).ThrowAsJavaScriptException();
|
|
214
|
+
return {};
|
|
215
|
+
}
|
|
216
|
+
std::string name = v.As<Napi::String>().Utf8Value();
|
|
217
|
+
if (name.empty()) {
|
|
218
|
+
std::string msg = std::string(context) + ": '" + field_name + "[" + std::to_string(i) + "]' must not be empty";
|
|
219
|
+
Napi::Error::New(env, msg).ThrowAsJavaScriptException();
|
|
220
|
+
return {};
|
|
221
|
+
}
|
|
222
|
+
result.push_back(std::move(name));
|
|
223
|
+
}
|
|
224
|
+
return result;
|
|
225
|
+
}
|
|
226
|
+
|
|
85
227
|
Napi::Object NftManager::Init(Napi::Env env, Napi::Object exports) {
|
|
86
228
|
Napi::Function func = DefineClass(env, "NftManager", {
|
|
87
229
|
InstanceMethod<&NftManager::CreateTable>("createTable"),
|
|
@@ -90,6 +232,10 @@ Napi::Object NftManager::Init(Napi::Env env, Napi::Object exports) {
|
|
|
90
232
|
InstanceMethod<&NftManager::AddAddresses>("addAddresses"),
|
|
91
233
|
InstanceMethod<&NftManager::RemoveAddresses>("removeAddresses"),
|
|
92
234
|
InstanceMethod<&NftManager::DeleteTable>("deleteTable"),
|
|
235
|
+
InstanceMethod<&NftManager::AddPort>("addPort"),
|
|
236
|
+
InstanceMethod<&NftManager::RemovePort>("removePort"),
|
|
237
|
+
InstanceMethod<&NftManager::AddPorts>("addPorts"),
|
|
238
|
+
InstanceMethod<&NftManager::RemovePorts>("removePorts"),
|
|
93
239
|
});
|
|
94
240
|
|
|
95
241
|
env.SetInstanceData(new Napi::FunctionReference(Napi::Persistent(func)));
|
|
@@ -102,73 +248,138 @@ NftManager::NftManager(const Napi::CallbackInfo& info)
|
|
|
102
248
|
: Napi::ObjectWrap<NftManager>(info) {
|
|
103
249
|
Napi::Env env = info.Env();
|
|
104
250
|
|
|
105
|
-
// 1. Validate options object exists
|
|
106
251
|
if (info.Length() < 1 || !info[0].IsObject()) {
|
|
107
252
|
Napi::TypeError::New(env,
|
|
108
|
-
"NftManager requires options object with tableName
|
|
253
|
+
"NftManager requires options object with tableName and sets")
|
|
109
254
|
.ThrowAsJavaScriptException();
|
|
110
255
|
return;
|
|
111
256
|
}
|
|
112
257
|
|
|
113
258
|
Napi::Object opts = info[0].As<Napi::Object>();
|
|
114
259
|
|
|
115
|
-
//
|
|
260
|
+
// tableName — required string
|
|
116
261
|
if (!opts.Has("tableName") || !opts.Get("tableName").IsString()) {
|
|
117
262
|
Napi::TypeError::New(env, "NftManager: 'tableName' is required and must be a string")
|
|
118
263
|
.ThrowAsJavaScriptException();
|
|
119
264
|
return;
|
|
120
265
|
}
|
|
121
|
-
|
|
122
|
-
|
|
266
|
+
|
|
267
|
+
// sets — required non-empty array
|
|
268
|
+
if (!opts.Has("sets") || !opts.Get("sets").IsArray()) {
|
|
269
|
+
Napi::TypeError::New(env, "NftManager: 'sets' is required and must be an array of strings")
|
|
123
270
|
.ThrowAsJavaScriptException();
|
|
124
271
|
return;
|
|
125
272
|
}
|
|
126
|
-
|
|
127
|
-
|
|
273
|
+
|
|
274
|
+
Napi::Array sets_arr = opts.Get("sets").As<Napi::Array>();
|
|
275
|
+
uint32_t len = sets_arr.Length();
|
|
276
|
+
|
|
277
|
+
if (len == 0) {
|
|
278
|
+
Napi::Error::New(env, "NftManager: 'sets' must contain at least one set name")
|
|
128
279
|
.ThrowAsJavaScriptException();
|
|
129
280
|
return;
|
|
130
281
|
}
|
|
131
282
|
|
|
283
|
+
// Parse required sets (InIP)
|
|
284
|
+
std::vector<std::string> in_sets;
|
|
285
|
+
in_sets.reserve(len);
|
|
286
|
+
for (uint32_t i = 0; i < len; ++i) {
|
|
287
|
+
Napi::Value val = sets_arr[i];
|
|
288
|
+
if (!val.IsString()) {
|
|
289
|
+
Napi::TypeError::New(env, "NftManager: 'sets[" + std::to_string(i) + "]' must be a string")
|
|
290
|
+
.ThrowAsJavaScriptException();
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
std::string name = val.As<Napi::String>().Utf8Value();
|
|
294
|
+
if (name.empty()) {
|
|
295
|
+
Napi::Error::New(env, "NftManager: 'sets[" + std::to_string(i) + "]' must not be empty")
|
|
296
|
+
.ThrowAsJavaScriptException();
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
in_sets.push_back(std::move(name));
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Parse optional outSets (OutIP)
|
|
303
|
+
std::vector<std::string> out_sets = parse_optional_string_array(env, opts, "outSets", "NftManager");
|
|
304
|
+
if (env.IsExceptionPending()) return;
|
|
305
|
+
|
|
306
|
+
// Parse optional outPortSets (OutPort)
|
|
307
|
+
std::vector<std::string> out_port_sets = parse_optional_string_array(env, opts, "outPortSets", "NftManager");
|
|
308
|
+
if (env.IsExceptionPending()) return;
|
|
309
|
+
|
|
310
|
+
// Cross-array duplicate check: all names must be unique across all arrays
|
|
311
|
+
std::vector<std::string> all_names;
|
|
312
|
+
all_names.reserve(in_sets.size() + out_sets.size() + out_port_sets.size());
|
|
313
|
+
all_names.insert(all_names.end(), in_sets.begin(), in_sets.end());
|
|
314
|
+
all_names.insert(all_names.end(), out_sets.begin(), out_sets.end());
|
|
315
|
+
all_names.insert(all_names.end(), out_port_sets.begin(), out_port_sets.end());
|
|
316
|
+
for (size_t i = 0; i < all_names.size(); ++i) {
|
|
317
|
+
for (size_t j = i + 1; j < all_names.size(); ++j) {
|
|
318
|
+
if (all_names[i] == all_names[j]) {
|
|
319
|
+
Napi::Error::New(env, "NftManager: duplicate set name '" + all_names[i] + "'")
|
|
320
|
+
.ThrowAsJavaScriptException();
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
132
326
|
std::string table_name = opts.Get("tableName").As<Napi::String>().Utf8Value();
|
|
133
|
-
std::string blacklist_set_name = opts.Get("blacklistSetName").As<Napi::String>().Utf8Value();
|
|
134
|
-
std::string droplist_set_name = opts.Get("droplistSetName").As<Napi::String>().Utf8Value();
|
|
135
327
|
|
|
136
|
-
// 3. Create NftConfig from names
|
|
137
328
|
config_ = std::make_shared<const nft::NftConfig>(
|
|
138
|
-
nft::NftConfig::from_names(table_name,
|
|
329
|
+
nft::NftConfig::from_names(table_name, in_sets, out_sets, out_port_sets));
|
|
139
330
|
|
|
140
|
-
// 4. Open netlink socket
|
|
141
331
|
sock_ = std::make_shared<NlSocket>();
|
|
142
332
|
|
|
143
|
-
// 5. Validate socket
|
|
144
333
|
if (!sock_->is_valid()) {
|
|
145
334
|
Napi::Error::New(env, "Failed to open netlink socket. Ensure CAP_NET_ADMIN or root.")
|
|
146
335
|
.ThrowAsJavaScriptException();
|
|
147
336
|
return;
|
|
148
337
|
}
|
|
338
|
+
|
|
339
|
+
valid_ = true;
|
|
149
340
|
}
|
|
150
341
|
|
|
342
|
+
// ── Table lifecycle ─────────────────────────────────────────────────────────
|
|
343
|
+
|
|
151
344
|
Napi::Value NftManager::CreateTable(const Napi::CallbackInfo& info) {
|
|
152
345
|
Napi::Env env = info.Env();
|
|
153
|
-
|
|
346
|
+
if (!valid_) {
|
|
347
|
+
Napi::Error::New(env, "NftManager is not initialized").ThrowAsJavaScriptException();
|
|
348
|
+
return env.Undefined();
|
|
349
|
+
}
|
|
154
350
|
auto deferred = Napi::Promise::Deferred::New(env);
|
|
155
351
|
auto promise = deferred.Promise();
|
|
156
352
|
Enqueue(std::make_unique<CreateTableOp>(config_), std::move(deferred));
|
|
157
353
|
return promise;
|
|
158
354
|
}
|
|
159
355
|
|
|
160
|
-
Napi::Value NftManager::
|
|
356
|
+
Napi::Value NftManager::DeleteTable(const Napi::CallbackInfo& info) {
|
|
161
357
|
Napi::Env env = info.Env();
|
|
358
|
+
if (!valid_) {
|
|
359
|
+
Napi::Error::New(env, "NftManager is not initialized").ThrowAsJavaScriptException();
|
|
360
|
+
return env.Undefined();
|
|
361
|
+
}
|
|
362
|
+
auto deferred = Napi::Promise::Deferred::New(env);
|
|
363
|
+
auto promise = deferred.Promise();
|
|
364
|
+
Enqueue(std::make_unique<DeleteTableOp>(config_), std::move(deferred));
|
|
365
|
+
return promise;
|
|
366
|
+
}
|
|
162
367
|
|
|
368
|
+
// ── IP address operations ───────────────────────────────────────────────────
|
|
369
|
+
|
|
370
|
+
Napi::Value NftManager::AddAddress(const Napi::CallbackInfo& info) {
|
|
371
|
+
Napi::Env env = info.Env();
|
|
372
|
+
if (!valid_) {
|
|
373
|
+
Napi::Error::New(env, "NftManager is not initialized").ThrowAsJavaScriptException();
|
|
374
|
+
return env.Undefined();
|
|
375
|
+
}
|
|
163
376
|
if (info.Length() < 1 || !info[0].IsObject()) {
|
|
164
377
|
Napi::TypeError::New(env, "addAddress requires an options object: { ip, set, timeout? }")
|
|
165
378
|
.ThrowAsJavaScriptException();
|
|
166
379
|
return env.Undefined();
|
|
167
380
|
}
|
|
168
|
-
|
|
169
381
|
Napi::Object opts = info[0].As<Napi::Object>();
|
|
170
382
|
|
|
171
|
-
// ip — required string
|
|
172
383
|
if (!opts.Has("ip") || !opts.Get("ip").IsString()) {
|
|
173
384
|
Napi::TypeError::New(env, "addAddress: 'ip' is required and must be a string")
|
|
174
385
|
.ThrowAsJavaScriptException();
|
|
@@ -176,25 +387,21 @@ Napi::Value NftManager::AddAddress(const Napi::CallbackInfo& info) {
|
|
|
176
387
|
}
|
|
177
388
|
std::string ip = opts.Get("ip").As<Napi::String>().Utf8Value();
|
|
178
389
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
if (!target) return env.Undefined();
|
|
390
|
+
auto set_idx = parse_set_name(env, opts, "addAddress", *config_, true, false);
|
|
391
|
+
if (!set_idx) return env.Undefined();
|
|
182
392
|
|
|
183
|
-
// timeout — optional number (seconds). If absent, 0 = permanent
|
|
184
393
|
auto timeout_ms = parse_timeout(env, opts, "addAddress");
|
|
185
394
|
if (!timeout_ms) return env.Undefined();
|
|
186
395
|
|
|
187
|
-
// Validate IP
|
|
188
396
|
IpAddr addr = parse_ip(ip);
|
|
189
397
|
if (addr.family == IpFamily::Invalid) {
|
|
190
|
-
Napi::Error::New(env, "Invalid IP address: " + ip)
|
|
191
|
-
.ThrowAsJavaScriptException();
|
|
398
|
+
Napi::Error::New(env, "Invalid IP address: " + ip).ThrowAsJavaScriptException();
|
|
192
399
|
return env.Undefined();
|
|
193
400
|
}
|
|
194
401
|
|
|
195
402
|
std::vector<ParsedAddr> addrs;
|
|
196
403
|
addrs.push_back(to_parsed_addr(addr));
|
|
197
|
-
auto op = std::make_unique<BulkAddSetElemOp>(std::move(addrs), *timeout_ms, config_, *
|
|
404
|
+
auto op = std::make_unique<BulkAddSetElemOp>(std::move(addrs), *timeout_ms, config_, *set_idx);
|
|
198
405
|
|
|
199
406
|
auto deferred = Napi::Promise::Deferred::New(env);
|
|
200
407
|
auto promise = deferred.Promise();
|
|
@@ -204,16 +411,17 @@ Napi::Value NftManager::AddAddress(const Napi::CallbackInfo& info) {
|
|
|
204
411
|
|
|
205
412
|
Napi::Value NftManager::RemoveAddress(const Napi::CallbackInfo& info) {
|
|
206
413
|
Napi::Env env = info.Env();
|
|
207
|
-
|
|
414
|
+
if (!valid_) {
|
|
415
|
+
Napi::Error::New(env, "NftManager is not initialized").ThrowAsJavaScriptException();
|
|
416
|
+
return env.Undefined();
|
|
417
|
+
}
|
|
208
418
|
if (info.Length() < 1 || !info[0].IsObject()) {
|
|
209
419
|
Napi::TypeError::New(env, "removeAddress requires an options object: { ip, set }")
|
|
210
420
|
.ThrowAsJavaScriptException();
|
|
211
421
|
return env.Undefined();
|
|
212
422
|
}
|
|
213
|
-
|
|
214
423
|
Napi::Object opts = info[0].As<Napi::Object>();
|
|
215
424
|
|
|
216
|
-
// ip — required string
|
|
217
425
|
if (!opts.Has("ip") || !opts.Get("ip").IsString()) {
|
|
218
426
|
Napi::TypeError::New(env, "removeAddress: 'ip' is required and must be a string")
|
|
219
427
|
.ThrowAsJavaScriptException();
|
|
@@ -221,21 +429,18 @@ Napi::Value NftManager::RemoveAddress(const Napi::CallbackInfo& info) {
|
|
|
221
429
|
}
|
|
222
430
|
std::string ip = opts.Get("ip").As<Napi::String>().Utf8Value();
|
|
223
431
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
if (!target) return env.Undefined();
|
|
432
|
+
auto set_idx = parse_set_name(env, opts, "removeAddress", *config_, true, false);
|
|
433
|
+
if (!set_idx) return env.Undefined();
|
|
227
434
|
|
|
228
|
-
// Validate IP
|
|
229
435
|
IpAddr addr = parse_ip(ip);
|
|
230
436
|
if (addr.family == IpFamily::Invalid) {
|
|
231
|
-
Napi::Error::New(env, "Invalid IP address: " + ip)
|
|
232
|
-
.ThrowAsJavaScriptException();
|
|
437
|
+
Napi::Error::New(env, "Invalid IP address: " + ip).ThrowAsJavaScriptException();
|
|
233
438
|
return env.Undefined();
|
|
234
439
|
}
|
|
235
440
|
|
|
236
441
|
std::vector<ParsedAddr> addrs;
|
|
237
442
|
addrs.push_back(to_parsed_addr(addr));
|
|
238
|
-
auto op = std::make_unique<BulkDelSetElemOp>(std::move(addrs), config_, *
|
|
443
|
+
auto op = std::make_unique<BulkDelSetElemOp>(std::move(addrs), config_, *set_idx);
|
|
239
444
|
|
|
240
445
|
auto deferred = Napi::Promise::Deferred::New(env);
|
|
241
446
|
auto promise = deferred.Promise();
|
|
@@ -245,16 +450,17 @@ Napi::Value NftManager::RemoveAddress(const Napi::CallbackInfo& info) {
|
|
|
245
450
|
|
|
246
451
|
Napi::Value NftManager::AddAddresses(const Napi::CallbackInfo& info) {
|
|
247
452
|
Napi::Env env = info.Env();
|
|
248
|
-
|
|
453
|
+
if (!valid_) {
|
|
454
|
+
Napi::Error::New(env, "NftManager is not initialized").ThrowAsJavaScriptException();
|
|
455
|
+
return env.Undefined();
|
|
456
|
+
}
|
|
249
457
|
if (info.Length() < 1 || !info[0].IsObject()) {
|
|
250
458
|
Napi::TypeError::New(env, "addAddresses requires an options object: { ips, set, timeout? }")
|
|
251
459
|
.ThrowAsJavaScriptException();
|
|
252
460
|
return env.Undefined();
|
|
253
461
|
}
|
|
254
|
-
|
|
255
462
|
Napi::Object opts = info[0].As<Napi::Object>();
|
|
256
463
|
|
|
257
|
-
// ips — required Array of strings
|
|
258
464
|
if (!opts.Has("ips") || !opts.Get("ips").IsArray()) {
|
|
259
465
|
Napi::TypeError::New(env, "addAddresses: 'ips' is required and must be an array of strings")
|
|
260
466
|
.ThrowAsJavaScriptException();
|
|
@@ -262,18 +468,15 @@ Napi::Value NftManager::AddAddresses(const Napi::CallbackInfo& info) {
|
|
|
262
468
|
}
|
|
263
469
|
Napi::Array arr = opts.Get("ips").As<Napi::Array>();
|
|
264
470
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
if (!target) return env.Undefined();
|
|
471
|
+
auto set_idx = parse_set_name(env, opts, "addAddresses", *config_, true, false);
|
|
472
|
+
if (!set_idx) return env.Undefined();
|
|
268
473
|
|
|
269
|
-
// timeout — optional number (seconds). If absent, 0 = permanent
|
|
270
474
|
auto timeout_ms = parse_timeout(env, opts, "addAddresses");
|
|
271
475
|
if (!timeout_ms) return env.Undefined();
|
|
272
476
|
|
|
273
477
|
std::vector<ParsedAddr> addrs = parse_ip_array(env, arr);
|
|
274
478
|
if (env.IsExceptionPending()) return env.Undefined();
|
|
275
479
|
|
|
276
|
-
// Empty arrays: early-exit with resolved promise
|
|
277
480
|
if (addrs.empty()) {
|
|
278
481
|
auto deferred = Napi::Promise::Deferred::New(env);
|
|
279
482
|
auto promise = deferred.Promise();
|
|
@@ -281,8 +484,7 @@ Napi::Value NftManager::AddAddresses(const Napi::CallbackInfo& info) {
|
|
|
281
484
|
return promise;
|
|
282
485
|
}
|
|
283
486
|
|
|
284
|
-
auto op = std::make_unique<BulkAddSetElemOp>(std::move(addrs), *timeout_ms, config_, *
|
|
285
|
-
|
|
487
|
+
auto op = std::make_unique<BulkAddSetElemOp>(std::move(addrs), *timeout_ms, config_, *set_idx);
|
|
286
488
|
auto deferred = Napi::Promise::Deferred::New(env);
|
|
287
489
|
auto promise = deferred.Promise();
|
|
288
490
|
Enqueue(std::move(op), std::move(deferred));
|
|
@@ -291,16 +493,17 @@ Napi::Value NftManager::AddAddresses(const Napi::CallbackInfo& info) {
|
|
|
291
493
|
|
|
292
494
|
Napi::Value NftManager::RemoveAddresses(const Napi::CallbackInfo& info) {
|
|
293
495
|
Napi::Env env = info.Env();
|
|
294
|
-
|
|
496
|
+
if (!valid_) {
|
|
497
|
+
Napi::Error::New(env, "NftManager is not initialized").ThrowAsJavaScriptException();
|
|
498
|
+
return env.Undefined();
|
|
499
|
+
}
|
|
295
500
|
if (info.Length() < 1 || !info[0].IsObject()) {
|
|
296
501
|
Napi::TypeError::New(env, "removeAddresses requires an options object: { ips, set }")
|
|
297
502
|
.ThrowAsJavaScriptException();
|
|
298
503
|
return env.Undefined();
|
|
299
504
|
}
|
|
300
|
-
|
|
301
505
|
Napi::Object opts = info[0].As<Napi::Object>();
|
|
302
506
|
|
|
303
|
-
// ips — required Array of strings
|
|
304
507
|
if (!opts.Has("ips") || !opts.Get("ips").IsArray()) {
|
|
305
508
|
Napi::TypeError::New(env, "removeAddresses: 'ips' is required and must be an array of strings")
|
|
306
509
|
.ThrowAsJavaScriptException();
|
|
@@ -308,14 +511,12 @@ Napi::Value NftManager::RemoveAddresses(const Napi::CallbackInfo& info) {
|
|
|
308
511
|
}
|
|
309
512
|
Napi::Array arr = opts.Get("ips").As<Napi::Array>();
|
|
310
513
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
if (!target) return env.Undefined();
|
|
514
|
+
auto set_idx = parse_set_name(env, opts, "removeAddresses", *config_, true, false);
|
|
515
|
+
if (!set_idx) return env.Undefined();
|
|
314
516
|
|
|
315
517
|
std::vector<ParsedAddr> addrs = parse_ip_array(env, arr);
|
|
316
518
|
if (env.IsExceptionPending()) return env.Undefined();
|
|
317
519
|
|
|
318
|
-
// Empty arrays: early-exit with resolved promise
|
|
319
520
|
if (addrs.empty()) {
|
|
320
521
|
auto deferred = Napi::Promise::Deferred::New(env);
|
|
321
522
|
auto promise = deferred.Promise();
|
|
@@ -323,7 +524,42 @@ Napi::Value NftManager::RemoveAddresses(const Napi::CallbackInfo& info) {
|
|
|
323
524
|
return promise;
|
|
324
525
|
}
|
|
325
526
|
|
|
326
|
-
auto op = std::make_unique<BulkDelSetElemOp>(std::move(addrs), config_, *
|
|
527
|
+
auto op = std::make_unique<BulkDelSetElemOp>(std::move(addrs), config_, *set_idx);
|
|
528
|
+
auto deferred = Napi::Promise::Deferred::New(env);
|
|
529
|
+
auto promise = deferred.Promise();
|
|
530
|
+
Enqueue(std::move(op), std::move(deferred));
|
|
531
|
+
return promise;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
// ── Port operations ─────────────────────────────────────────────────────────
|
|
535
|
+
|
|
536
|
+
Napi::Value NftManager::AddPort(const Napi::CallbackInfo& info) {
|
|
537
|
+
Napi::Env env = info.Env();
|
|
538
|
+
if (!valid_) {
|
|
539
|
+
Napi::Error::New(env, "NftManager is not initialized").ThrowAsJavaScriptException();
|
|
540
|
+
return env.Undefined();
|
|
541
|
+
}
|
|
542
|
+
if (info.Length() < 1 || !info[0].IsObject()) {
|
|
543
|
+
Napi::TypeError::New(env, "addPort requires an options object: { port, set, timeout? }")
|
|
544
|
+
.ThrowAsJavaScriptException();
|
|
545
|
+
return env.Undefined();
|
|
546
|
+
}
|
|
547
|
+
Napi::Object opts = info[0].As<Napi::Object>();
|
|
548
|
+
|
|
549
|
+
auto port = parse_port_value(env, opts, "addPort");
|
|
550
|
+
if (!port) return env.Undefined();
|
|
551
|
+
|
|
552
|
+
auto set_idx = parse_set_name(env, opts, "addPort", *config_, false, true);
|
|
553
|
+
if (!set_idx) return env.Undefined();
|
|
554
|
+
|
|
555
|
+
uint8_t proto = parse_protocol(env, opts, "addPort");
|
|
556
|
+
if (proto == 255) return env.Undefined();
|
|
557
|
+
|
|
558
|
+
auto timeout_ms = parse_timeout(env, opts, "addPort");
|
|
559
|
+
if (!timeout_ms) return env.Undefined();
|
|
560
|
+
|
|
561
|
+
auto elems = make_port_elems(*port, proto);
|
|
562
|
+
auto op = std::make_unique<BulkAddPortElemOp>(std::move(elems), *timeout_ms, config_, *set_idx);
|
|
327
563
|
|
|
328
564
|
auto deferred = Napi::Promise::Deferred::New(env);
|
|
329
565
|
auto promise = deferred.Promise();
|
|
@@ -331,15 +567,130 @@ Napi::Value NftManager::RemoveAddresses(const Napi::CallbackInfo& info) {
|
|
|
331
567
|
return promise;
|
|
332
568
|
}
|
|
333
569
|
|
|
334
|
-
Napi::Value NftManager::
|
|
570
|
+
Napi::Value NftManager::RemovePort(const Napi::CallbackInfo& info) {
|
|
335
571
|
Napi::Env env = info.Env();
|
|
572
|
+
if (!valid_) {
|
|
573
|
+
Napi::Error::New(env, "NftManager is not initialized").ThrowAsJavaScriptException();
|
|
574
|
+
return env.Undefined();
|
|
575
|
+
}
|
|
576
|
+
if (info.Length() < 1 || !info[0].IsObject()) {
|
|
577
|
+
Napi::TypeError::New(env, "removePort requires an options object: { port, set }")
|
|
578
|
+
.ThrowAsJavaScriptException();
|
|
579
|
+
return env.Undefined();
|
|
580
|
+
}
|
|
581
|
+
Napi::Object opts = info[0].As<Napi::Object>();
|
|
582
|
+
|
|
583
|
+
auto port = parse_port_value(env, opts, "removePort");
|
|
584
|
+
if (!port) return env.Undefined();
|
|
585
|
+
|
|
586
|
+
auto set_idx = parse_set_name(env, opts, "removePort", *config_, false, true);
|
|
587
|
+
if (!set_idx) return env.Undefined();
|
|
588
|
+
|
|
589
|
+
uint8_t proto = parse_protocol(env, opts, "removePort");
|
|
590
|
+
if (proto == 255) return env.Undefined();
|
|
591
|
+
|
|
592
|
+
auto elems = make_port_elems(*port, proto);
|
|
593
|
+
auto op = std::make_unique<BulkDelPortElemOp>(std::move(elems), config_, *set_idx);
|
|
336
594
|
|
|
337
595
|
auto deferred = Napi::Promise::Deferred::New(env);
|
|
338
596
|
auto promise = deferred.Promise();
|
|
339
|
-
Enqueue(std::
|
|
597
|
+
Enqueue(std::move(op), std::move(deferred));
|
|
598
|
+
return promise;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
Napi::Value NftManager::AddPorts(const Napi::CallbackInfo& info) {
|
|
602
|
+
Napi::Env env = info.Env();
|
|
603
|
+
if (!valid_) {
|
|
604
|
+
Napi::Error::New(env, "NftManager is not initialized").ThrowAsJavaScriptException();
|
|
605
|
+
return env.Undefined();
|
|
606
|
+
}
|
|
607
|
+
if (info.Length() < 1 || !info[0].IsObject()) {
|
|
608
|
+
Napi::TypeError::New(env, "addPorts requires an options object: { ports, set, timeout? }")
|
|
609
|
+
.ThrowAsJavaScriptException();
|
|
610
|
+
return env.Undefined();
|
|
611
|
+
}
|
|
612
|
+
Napi::Object opts = info[0].As<Napi::Object>();
|
|
613
|
+
|
|
614
|
+
if (!opts.Has("ports") || !opts.Get("ports").IsArray()) {
|
|
615
|
+
Napi::TypeError::New(env, "addPorts: 'ports' is required and must be an array of numbers")
|
|
616
|
+
.ThrowAsJavaScriptException();
|
|
617
|
+
return env.Undefined();
|
|
618
|
+
}
|
|
619
|
+
Napi::Array arr = opts.Get("ports").As<Napi::Array>();
|
|
620
|
+
|
|
621
|
+
auto set_idx = parse_set_name(env, opts, "addPorts", *config_, false, true);
|
|
622
|
+
if (!set_idx) return env.Undefined();
|
|
623
|
+
|
|
624
|
+
uint8_t proto = parse_protocol(env, opts, "addPorts");
|
|
625
|
+
if (proto == 255) return env.Undefined();
|
|
626
|
+
|
|
627
|
+
auto timeout_ms = parse_timeout(env, opts, "addPorts");
|
|
628
|
+
if (!timeout_ms) return env.Undefined();
|
|
629
|
+
|
|
630
|
+
std::vector<uint16_t> ports = parse_port_array(env, arr, "addPorts");
|
|
631
|
+
if (env.IsExceptionPending()) return env.Undefined();
|
|
632
|
+
|
|
633
|
+
if (ports.empty()) {
|
|
634
|
+
auto deferred = Napi::Promise::Deferred::New(env);
|
|
635
|
+
auto promise = deferred.Promise();
|
|
636
|
+
deferred.Resolve(env.Undefined());
|
|
637
|
+
return promise;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
auto elems = make_port_elems_bulk(ports, proto);
|
|
641
|
+
auto op = std::make_unique<BulkAddPortElemOp>(std::move(elems), *timeout_ms, config_, *set_idx);
|
|
642
|
+
auto deferred = Napi::Promise::Deferred::New(env);
|
|
643
|
+
auto promise = deferred.Promise();
|
|
644
|
+
Enqueue(std::move(op), std::move(deferred));
|
|
645
|
+
return promise;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
Napi::Value NftManager::RemovePorts(const Napi::CallbackInfo& info) {
|
|
649
|
+
Napi::Env env = info.Env();
|
|
650
|
+
if (!valid_) {
|
|
651
|
+
Napi::Error::New(env, "NftManager is not initialized").ThrowAsJavaScriptException();
|
|
652
|
+
return env.Undefined();
|
|
653
|
+
}
|
|
654
|
+
if (info.Length() < 1 || !info[0].IsObject()) {
|
|
655
|
+
Napi::TypeError::New(env, "removePorts requires an options object: { ports, set }")
|
|
656
|
+
.ThrowAsJavaScriptException();
|
|
657
|
+
return env.Undefined();
|
|
658
|
+
}
|
|
659
|
+
Napi::Object opts = info[0].As<Napi::Object>();
|
|
660
|
+
|
|
661
|
+
if (!opts.Has("ports") || !opts.Get("ports").IsArray()) {
|
|
662
|
+
Napi::TypeError::New(env, "removePorts: 'ports' is required and must be an array of numbers")
|
|
663
|
+
.ThrowAsJavaScriptException();
|
|
664
|
+
return env.Undefined();
|
|
665
|
+
}
|
|
666
|
+
Napi::Array arr = opts.Get("ports").As<Napi::Array>();
|
|
667
|
+
|
|
668
|
+
auto set_idx = parse_set_name(env, opts, "removePorts", *config_, false, true);
|
|
669
|
+
if (!set_idx) return env.Undefined();
|
|
670
|
+
|
|
671
|
+
uint8_t proto = parse_protocol(env, opts, "removePorts");
|
|
672
|
+
if (proto == 255) return env.Undefined();
|
|
673
|
+
|
|
674
|
+
std::vector<uint16_t> ports = parse_port_array(env, arr, "removePorts");
|
|
675
|
+
if (env.IsExceptionPending()) return env.Undefined();
|
|
676
|
+
|
|
677
|
+
if (ports.empty()) {
|
|
678
|
+
auto deferred = Napi::Promise::Deferred::New(env);
|
|
679
|
+
auto promise = deferred.Promise();
|
|
680
|
+
deferred.Resolve(env.Undefined());
|
|
681
|
+
return promise;
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
auto elems = make_port_elems_bulk(ports, proto);
|
|
685
|
+
auto op = std::make_unique<BulkDelPortElemOp>(std::move(elems), config_, *set_idx);
|
|
686
|
+
auto deferred = Napi::Promise::Deferred::New(env);
|
|
687
|
+
auto promise = deferred.Promise();
|
|
688
|
+
Enqueue(std::move(op), std::move(deferred));
|
|
340
689
|
return promise;
|
|
341
690
|
}
|
|
342
691
|
|
|
692
|
+
// ── Queue management ────────────────────────────────────────────────────────
|
|
693
|
+
|
|
343
694
|
void NftManager::Enqueue(std::unique_ptr<NlOperation> op, Napi::Promise::Deferred deferred) {
|
|
344
695
|
queue_.push({std::move(op), std::move(deferred)});
|
|
345
696
|
DrainQueue();
|