nftables-napi 0.3.0 → 0.4.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/lib/index.d.ts +10 -10
- package/package.json +1 -1
- package/prebuilds/linux-arm64/nftables-napi.node +0 -0
- package/prebuilds/linux-x64/nftables-napi.node +0 -0
- package/src/netlink/constants.h +6 -0
- package/src/netlink/parsed_addr.h +2 -1
- package/src/netlink/set_ops.cpp +4 -3
- package/src/netlink/table_ops.cpp +6 -3
- package/src/nft_manager.cpp +34 -33
- package/src/validation.cpp +155 -0
- package/src/validation.h +12 -3
package/lib/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Native nftables manager for Linux firewall.
|
|
3
|
-
* Manages IPv4/IPv6 tables with dynamic sets via libnftnl + libmnl (direct netlink, no nft CLI).
|
|
3
|
+
* Manages IPv4/IPv6 tables with dynamic sets and CIDR ranges via libnftnl + libmnl (direct netlink, no nft CLI).
|
|
4
4
|
* Requires CAP_NET_ADMIN or root privileges.
|
|
5
5
|
*/
|
|
6
6
|
|
|
@@ -31,7 +31,7 @@ export interface NftManagerOptions {
|
|
|
31
31
|
|
|
32
32
|
/** Options for adding a single address. */
|
|
33
33
|
export interface AddAddressOptions {
|
|
34
|
-
/** IPv4 or
|
|
34
|
+
/** IPv4/IPv6 address or CIDR (e.g., "1.2.3.4", "10.0.0.0/8", "2001:db8::/32"). */
|
|
35
35
|
ip: string;
|
|
36
36
|
/** Target set name (must match one from constructor's ingressAddrSets or egressAddrSets). */
|
|
37
37
|
set: string;
|
|
@@ -41,7 +41,7 @@ export interface AddAddressOptions {
|
|
|
41
41
|
|
|
42
42
|
/** Options for removing a single address. */
|
|
43
43
|
export interface RemoveAddressOptions {
|
|
44
|
-
/** IPv4 or
|
|
44
|
+
/** IPv4/IPv6 address or CIDR to remove (must match exactly as added). */
|
|
45
45
|
ip: string;
|
|
46
46
|
/** Target set name (must match one from constructor's ingressAddrSets or egressAddrSets). */
|
|
47
47
|
set: string;
|
|
@@ -49,7 +49,7 @@ export interface RemoveAddressOptions {
|
|
|
49
49
|
|
|
50
50
|
/** Options for bulk adding addresses. */
|
|
51
51
|
export interface AddAddressesOptions {
|
|
52
|
-
/** Array of IPv4/IPv6 addresses. */
|
|
52
|
+
/** Array of IPv4/IPv6 addresses or CIDRs. */
|
|
53
53
|
ips: string[];
|
|
54
54
|
/** Target set name (must match one from constructor's ingressAddrSets or egressAddrSets). */
|
|
55
55
|
set: string;
|
|
@@ -59,7 +59,7 @@ export interface AddAddressesOptions {
|
|
|
59
59
|
|
|
60
60
|
/** Options for bulk removing addresses. */
|
|
61
61
|
export interface RemoveAddressesOptions {
|
|
62
|
-
/** Array of IPv4/IPv6 addresses to remove. */
|
|
62
|
+
/** Array of IPv4/IPv6 addresses or CIDRs to remove. */
|
|
63
63
|
ips: string[];
|
|
64
64
|
/** Target set name (must match one from constructor's ingressAddrSets or egressAddrSets). */
|
|
65
65
|
set: string;
|
|
@@ -145,8 +145,8 @@ export class NftManager {
|
|
|
145
145
|
deleteTable(): Promise<void>;
|
|
146
146
|
|
|
147
147
|
/**
|
|
148
|
-
* Adds an IP address to a set.
|
|
149
|
-
* Auto-detects IPv4 vs IPv6 and routes to the correct table/set.
|
|
148
|
+
* Adds an IP address or CIDR range to a set.
|
|
149
|
+
* Auto-detects IPv4 vs IPv6 and routes to the correct table/set. Accepts CIDR notation (e.g., "10.0.0.0/8").
|
|
150
150
|
* Works with both input sets (ingressAddrSets) and output sets (egressAddrSets).
|
|
151
151
|
*
|
|
152
152
|
* @param options - Address, target set name, and optional timeout.
|
|
@@ -156,7 +156,7 @@ export class NftManager {
|
|
|
156
156
|
addAddress(options: AddAddressOptions): Promise<void>;
|
|
157
157
|
|
|
158
158
|
/**
|
|
159
|
-
* Removes an IP address from a set.
|
|
159
|
+
* Removes an IP address or CIDR range from a set.
|
|
160
160
|
* Idempotent — no error if IP is not in the set.
|
|
161
161
|
* Works with both input sets (ingressAddrSets) and output sets (egressAddrSets).
|
|
162
162
|
*
|
|
@@ -167,7 +167,7 @@ export class NftManager {
|
|
|
167
167
|
removeAddress(options: RemoveAddressOptions): Promise<void>;
|
|
168
168
|
|
|
169
169
|
/**
|
|
170
|
-
* Adds multiple IP addresses to a set in bulk.
|
|
170
|
+
* Adds multiple IP addresses or CIDR ranges to a set in bulk.
|
|
171
171
|
* Empty arrays are a no-op.
|
|
172
172
|
*
|
|
173
173
|
* @param options - Array of addresses, target set name, and optional timeout.
|
|
@@ -177,7 +177,7 @@ export class NftManager {
|
|
|
177
177
|
addAddresses(options: AddAddressesOptions): Promise<void>;
|
|
178
178
|
|
|
179
179
|
/**
|
|
180
|
-
* Removes multiple IP addresses from a set in bulk.
|
|
180
|
+
* Removes multiple IP addresses or CIDR ranges from a set in bulk.
|
|
181
181
|
* Idempotent — no error if IPs are not in the set.
|
|
182
182
|
* Empty arrays are a no-op.
|
|
183
183
|
*
|
package/package.json
CHANGED
|
Binary file
|
|
Binary file
|
package/src/netlink/constants.h
CHANGED
|
@@ -54,6 +54,12 @@ inline constexpr uint32_t TRANSPORT_DPORT_LEN = 2;
|
|
|
54
54
|
inline constexpr uint32_t DATATYPE_PROTO_SERVICE = (12 << 6 | 13);
|
|
55
55
|
inline constexpr uint32_t PROTO_SERVICE_KEY_LEN = 8;
|
|
56
56
|
|
|
57
|
+
// Byte offsets within the 8-byte concatenated (proto . port) key
|
|
58
|
+
// Layout: [proto:1][pad:3][port_hi:1][port_lo:1][pad:2]
|
|
59
|
+
inline constexpr uint32_t PORT_KEY_PROTO_OFFSET = 0;
|
|
60
|
+
inline constexpr uint32_t PORT_KEY_PORT_HI_OFFSET = 4;
|
|
61
|
+
inline constexpr uint32_t PORT_KEY_PORT_LO_OFFSET = 5;
|
|
62
|
+
|
|
57
63
|
// L4 protocol numbers
|
|
58
64
|
inline constexpr uint8_t PROTO_TCP = 6;
|
|
59
65
|
inline constexpr uint8_t PROTO_UDP = 17;
|
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
struct ParsedAddr {
|
|
6
6
|
uint32_t family; // nft::FAMILY_IPV4 or nft::FAMILY_IPV6 (== NFPROTO_IPV4/IPV6)
|
|
7
|
-
uint8_t bytes[16];
|
|
7
|
+
uint8_t bytes[16]; // key (start of interval)
|
|
8
|
+
uint8_t end_bytes[16]; // key_end (exclusive end of interval)
|
|
8
9
|
uint32_t len; // 4 for IPv4, 16 for IPv6
|
|
9
10
|
};
|
|
10
11
|
|
package/src/netlink/set_ops.cpp
CHANGED
|
@@ -67,6 +67,7 @@ static NlResult bulk_set_elem_op(
|
|
|
67
67
|
auto* e = nftnl_set_elem_alloc();
|
|
68
68
|
if (!e) return {false, "nftnl_set_elem_alloc failed"};
|
|
69
69
|
nftnl_set_elem_set(e, NFTNL_SET_ELEM_KEY, family_addrs[i]->bytes, family_addrs[i]->len);
|
|
70
|
+
nftnl_set_elem_set(e, NFTNL_SET_ELEM_KEY_END, family_addrs[i]->end_bytes, family_addrs[i]->len);
|
|
70
71
|
if (timeout_ms > 0) {
|
|
71
72
|
nftnl_set_elem_set_u64(e, NFTNL_SET_ELEM_TIMEOUT, timeout_ms);
|
|
72
73
|
}
|
|
@@ -154,9 +155,9 @@ static NlResult bulk_port_elem_op(
|
|
|
154
155
|
if (!e) return {false, "nftnl_set_elem_alloc failed"};
|
|
155
156
|
// 8-byte concatenated key: [proto][pad:3][port_hi][port_lo][pad:2]
|
|
156
157
|
uint8_t key[8] = {};
|
|
157
|
-
key[
|
|
158
|
-
key[
|
|
159
|
-
key[
|
|
158
|
+
key[nft::PORT_KEY_PROTO_OFFSET] = elems[i].proto;
|
|
159
|
+
key[nft::PORT_KEY_PORT_HI_OFFSET] = static_cast<uint8_t>(elems[i].port >> 8);
|
|
160
|
+
key[nft::PORT_KEY_PORT_LO_OFFSET] = static_cast<uint8_t>(elems[i].port & 0xFF);
|
|
160
161
|
nftnl_set_elem_set(e, NFTNL_SET_ELEM_KEY, key, sizeof(key));
|
|
161
162
|
if (timeout_ms > 0) {
|
|
162
163
|
nftnl_set_elem_set_u64(e, NFTNL_SET_ELEM_TIMEOUT, timeout_ms);
|
|
@@ -69,7 +69,7 @@ static bool add_counter_obj(NlBatch& batch, uint32_t family, const char* table,
|
|
|
69
69
|
|
|
70
70
|
static bool add_set(NlBatch& batch, uint32_t family, const char* table,
|
|
71
71
|
const char* name, uint32_t key_type, uint32_t key_len,
|
|
72
|
-
uint32_t set_id,
|
|
72
|
+
uint32_t set_id, bool interval = false,
|
|
73
73
|
const uint8_t* concat_field_lens = nullptr,
|
|
74
74
|
size_t concat_field_count = 0) {
|
|
75
75
|
auto s = nft::make_set();
|
|
@@ -81,6 +81,8 @@ static bool add_set(NlBatch& batch, uint32_t family, const char* table,
|
|
|
81
81
|
nftnl_set_set_u32(s.get(), NFTNL_SET_KEY_LEN, key_len);
|
|
82
82
|
|
|
83
83
|
uint32_t set_flags = NFT_SET_TIMEOUT | NFT_SET_EXPR;
|
|
84
|
+
if (interval)
|
|
85
|
+
set_flags |= NFT_SET_INTERVAL;
|
|
84
86
|
if (concat_field_lens && concat_field_count > 0)
|
|
85
87
|
set_flags |= NFT_SET_CONCAT;
|
|
86
88
|
nftnl_set_set_u32(s.get(), NFTNL_SET_FLAGS, set_flags);
|
|
@@ -297,10 +299,11 @@ NlResult CreateTableOp::execute(NlSocket& sock) {
|
|
|
297
299
|
key_len_v4 = IPV4_ADDR_LEN;
|
|
298
300
|
key_len_v6 = IPV6_ADDR_LEN;
|
|
299
301
|
}
|
|
302
|
+
bool is_interval = (sd.kind != SetKind::OutPort);
|
|
300
303
|
if (!add_set(batch, NFPROTO_IPV4, cfg_->table_v4.c_str(), sd.name.c_str(),
|
|
301
|
-
key_type_v4, key_len_v4, sid++, concat_fields, concat_count)
|
|
304
|
+
key_type_v4, key_len_v4, sid++, is_interval, concat_fields, concat_count)
|
|
302
305
|
|| !add_set(batch, NFPROTO_IPV6, cfg_->table_v6.c_str(), sd.name_v6.c_str(),
|
|
303
|
-
key_type_v6, key_len_v6, sid++, concat_fields, concat_count))
|
|
306
|
+
key_type_v6, key_len_v6, sid++, is_interval, concat_fields, concat_count))
|
|
304
307
|
return {false, "failed to build set '" + sd.name + "'"};
|
|
305
308
|
}
|
|
306
309
|
|
package/src/nft_manager.cpp
CHANGED
|
@@ -13,11 +13,12 @@
|
|
|
13
13
|
|
|
14
14
|
static constexpr double MAX_TIMEOUT_SEC = 4294967295.0; // UINT32_MAX seconds (~136 years)
|
|
15
15
|
|
|
16
|
-
static ParsedAddr to_parsed_addr(const
|
|
16
|
+
static ParsedAddr to_parsed_addr(const CidrAddr& cidr) {
|
|
17
17
|
ParsedAddr pa{};
|
|
18
|
-
pa.family = (
|
|
19
|
-
pa.len =
|
|
20
|
-
std::memcpy(pa.bytes,
|
|
18
|
+
pa.family = (cidr.network.family == IpFamily::IPv4) ? nft::FAMILY_IPV4 : nft::FAMILY_IPV6;
|
|
19
|
+
pa.len = cidr.network.len;
|
|
20
|
+
std::memcpy(pa.bytes, cidr.network.bytes.data(), cidr.network.len);
|
|
21
|
+
std::memcpy(pa.end_bytes, cidr.end.bytes.data(), cidr.end.len);
|
|
21
22
|
return pa;
|
|
22
23
|
}
|
|
23
24
|
|
|
@@ -34,14 +35,14 @@ static std::vector<ParsedAddr> parse_ip_array(Napi::Env env, Napi::Array arr) {
|
|
|
34
35
|
}
|
|
35
36
|
|
|
36
37
|
std::string ip = val.As<Napi::String>().Utf8Value();
|
|
37
|
-
|
|
38
|
-
if (
|
|
39
|
-
Napi::Error::New(env, "Invalid IP address at index " + std::to_string(i) + ": " + ip)
|
|
38
|
+
CidrAddr cidr = parse_ip_or_cidr(ip);
|
|
39
|
+
if (cidr.network.family == IpFamily::Invalid) {
|
|
40
|
+
Napi::Error::New(env, "Invalid IP address or CIDR at index " + std::to_string(i) + ": " + ip)
|
|
40
41
|
.ThrowAsJavaScriptException();
|
|
41
42
|
return {};
|
|
42
43
|
}
|
|
43
44
|
|
|
44
|
-
addrs.push_back(to_parsed_addr(
|
|
45
|
+
addrs.push_back(to_parsed_addr(cidr));
|
|
45
46
|
}
|
|
46
47
|
|
|
47
48
|
return addrs;
|
|
@@ -147,23 +148,23 @@ static std::vector<uint16_t> parse_port_array(Napi::Env env, Napi::Array arr,
|
|
|
147
148
|
|
|
148
149
|
// Parse optional protocol: 'tcp', 'udp', or absent (both).
|
|
149
150
|
// Returns 0 for both, PROTO_TCP for tcp, PROTO_UDP for udp.
|
|
150
|
-
// Returns
|
|
151
|
-
static uint8_t parse_protocol(Napi::Env env, Napi::Object opts, const char* method_name) {
|
|
151
|
+
// Returns std::nullopt on error (after throwing JS exception).
|
|
152
|
+
static std::optional<uint8_t> parse_protocol(Napi::Env env, Napi::Object opts, const char* method_name) {
|
|
152
153
|
if (!opts.Has("protocol") || opts.Get("protocol").IsUndefined() || opts.Get("protocol").IsNull()) {
|
|
153
|
-
return 0; // both
|
|
154
|
+
return uint8_t{0}; // both
|
|
154
155
|
}
|
|
155
156
|
Napi::Value pv = opts.Get("protocol");
|
|
156
157
|
if (!pv.IsString()) {
|
|
157
158
|
std::string msg = std::string(method_name) + ": 'protocol' must be 'tcp' or 'udp'";
|
|
158
159
|
Napi::TypeError::New(env, msg).ThrowAsJavaScriptException();
|
|
159
|
-
return
|
|
160
|
+
return std::nullopt;
|
|
160
161
|
}
|
|
161
162
|
std::string proto = pv.As<Napi::String>().Utf8Value();
|
|
162
163
|
if (proto == "tcp") return nft::PROTO_TCP;
|
|
163
164
|
if (proto == "udp") return nft::PROTO_UDP;
|
|
164
165
|
std::string msg = std::string(method_name) + ": 'protocol' must be 'tcp' or 'udp'";
|
|
165
166
|
Napi::Error::New(env, msg).ThrowAsJavaScriptException();
|
|
166
|
-
return
|
|
167
|
+
return std::nullopt;
|
|
167
168
|
}
|
|
168
169
|
|
|
169
170
|
static std::vector<PortElem> make_port_elems(uint16_t port, uint8_t proto) {
|
|
@@ -393,14 +394,14 @@ Napi::Value NftManager::AddAddress(const Napi::CallbackInfo& info) {
|
|
|
393
394
|
auto timeout_ms = parse_timeout(env, opts, "addAddress");
|
|
394
395
|
if (!timeout_ms) return env.Undefined();
|
|
395
396
|
|
|
396
|
-
|
|
397
|
-
if (
|
|
398
|
-
Napi::Error::New(env, "Invalid IP address: " + ip).ThrowAsJavaScriptException();
|
|
397
|
+
CidrAddr cidr = parse_ip_or_cidr(ip);
|
|
398
|
+
if (cidr.network.family == IpFamily::Invalid) {
|
|
399
|
+
Napi::Error::New(env, "Invalid IP address or CIDR: " + ip).ThrowAsJavaScriptException();
|
|
399
400
|
return env.Undefined();
|
|
400
401
|
}
|
|
401
402
|
|
|
402
403
|
std::vector<ParsedAddr> addrs;
|
|
403
|
-
addrs.push_back(to_parsed_addr(
|
|
404
|
+
addrs.push_back(to_parsed_addr(cidr));
|
|
404
405
|
auto op = std::make_unique<BulkAddSetElemOp>(std::move(addrs), *timeout_ms, config_, *set_idx);
|
|
405
406
|
|
|
406
407
|
auto deferred = Napi::Promise::Deferred::New(env);
|
|
@@ -432,14 +433,14 @@ Napi::Value NftManager::RemoveAddress(const Napi::CallbackInfo& info) {
|
|
|
432
433
|
auto set_idx = parse_set_name(env, opts, "removeAddress", *config_, true, false);
|
|
433
434
|
if (!set_idx) return env.Undefined();
|
|
434
435
|
|
|
435
|
-
|
|
436
|
-
if (
|
|
437
|
-
Napi::Error::New(env, "Invalid IP address: " + ip).ThrowAsJavaScriptException();
|
|
436
|
+
CidrAddr cidr = parse_ip_or_cidr(ip);
|
|
437
|
+
if (cidr.network.family == IpFamily::Invalid) {
|
|
438
|
+
Napi::Error::New(env, "Invalid IP address or CIDR: " + ip).ThrowAsJavaScriptException();
|
|
438
439
|
return env.Undefined();
|
|
439
440
|
}
|
|
440
441
|
|
|
441
442
|
std::vector<ParsedAddr> addrs;
|
|
442
|
-
addrs.push_back(to_parsed_addr(
|
|
443
|
+
addrs.push_back(to_parsed_addr(cidr));
|
|
443
444
|
auto op = std::make_unique<BulkDelSetElemOp>(std::move(addrs), config_, *set_idx);
|
|
444
445
|
|
|
445
446
|
auto deferred = Napi::Promise::Deferred::New(env);
|
|
@@ -552,13 +553,13 @@ Napi::Value NftManager::AddPort(const Napi::CallbackInfo& info) {
|
|
|
552
553
|
auto set_idx = parse_set_name(env, opts, "addPort", *config_, false, true);
|
|
553
554
|
if (!set_idx) return env.Undefined();
|
|
554
555
|
|
|
555
|
-
|
|
556
|
-
if (proto
|
|
556
|
+
auto proto = parse_protocol(env, opts, "addPort");
|
|
557
|
+
if (!proto) return env.Undefined();
|
|
557
558
|
|
|
558
559
|
auto timeout_ms = parse_timeout(env, opts, "addPort");
|
|
559
560
|
if (!timeout_ms) return env.Undefined();
|
|
560
561
|
|
|
561
|
-
auto elems = make_port_elems(*port, proto);
|
|
562
|
+
auto elems = make_port_elems(*port, *proto);
|
|
562
563
|
auto op = std::make_unique<BulkAddPortElemOp>(std::move(elems), *timeout_ms, config_, *set_idx);
|
|
563
564
|
|
|
564
565
|
auto deferred = Napi::Promise::Deferred::New(env);
|
|
@@ -586,10 +587,10 @@ Napi::Value NftManager::RemovePort(const Napi::CallbackInfo& info) {
|
|
|
586
587
|
auto set_idx = parse_set_name(env, opts, "removePort", *config_, false, true);
|
|
587
588
|
if (!set_idx) return env.Undefined();
|
|
588
589
|
|
|
589
|
-
|
|
590
|
-
if (proto
|
|
590
|
+
auto proto = parse_protocol(env, opts, "removePort");
|
|
591
|
+
if (!proto) return env.Undefined();
|
|
591
592
|
|
|
592
|
-
auto elems = make_port_elems(*port, proto);
|
|
593
|
+
auto elems = make_port_elems(*port, *proto);
|
|
593
594
|
auto op = std::make_unique<BulkDelPortElemOp>(std::move(elems), config_, *set_idx);
|
|
594
595
|
|
|
595
596
|
auto deferred = Napi::Promise::Deferred::New(env);
|
|
@@ -621,8 +622,8 @@ Napi::Value NftManager::AddPorts(const Napi::CallbackInfo& info) {
|
|
|
621
622
|
auto set_idx = parse_set_name(env, opts, "addPorts", *config_, false, true);
|
|
622
623
|
if (!set_idx) return env.Undefined();
|
|
623
624
|
|
|
624
|
-
|
|
625
|
-
if (proto
|
|
625
|
+
auto proto = parse_protocol(env, opts, "addPorts");
|
|
626
|
+
if (!proto) return env.Undefined();
|
|
626
627
|
|
|
627
628
|
auto timeout_ms = parse_timeout(env, opts, "addPorts");
|
|
628
629
|
if (!timeout_ms) return env.Undefined();
|
|
@@ -637,7 +638,7 @@ Napi::Value NftManager::AddPorts(const Napi::CallbackInfo& info) {
|
|
|
637
638
|
return promise;
|
|
638
639
|
}
|
|
639
640
|
|
|
640
|
-
auto elems = make_port_elems_bulk(ports, proto);
|
|
641
|
+
auto elems = make_port_elems_bulk(ports, *proto);
|
|
641
642
|
auto op = std::make_unique<BulkAddPortElemOp>(std::move(elems), *timeout_ms, config_, *set_idx);
|
|
642
643
|
auto deferred = Napi::Promise::Deferred::New(env);
|
|
643
644
|
auto promise = deferred.Promise();
|
|
@@ -668,8 +669,8 @@ Napi::Value NftManager::RemovePorts(const Napi::CallbackInfo& info) {
|
|
|
668
669
|
auto set_idx = parse_set_name(env, opts, "removePorts", *config_, false, true);
|
|
669
670
|
if (!set_idx) return env.Undefined();
|
|
670
671
|
|
|
671
|
-
|
|
672
|
-
if (proto
|
|
672
|
+
auto proto = parse_protocol(env, opts, "removePorts");
|
|
673
|
+
if (!proto) return env.Undefined();
|
|
673
674
|
|
|
674
675
|
std::vector<uint16_t> ports = parse_port_array(env, arr, "removePorts");
|
|
675
676
|
if (env.IsExceptionPending()) return env.Undefined();
|
|
@@ -681,7 +682,7 @@ Napi::Value NftManager::RemovePorts(const Napi::CallbackInfo& info) {
|
|
|
681
682
|
return promise;
|
|
682
683
|
}
|
|
683
684
|
|
|
684
|
-
auto elems = make_port_elems_bulk(ports, proto);
|
|
685
|
+
auto elems = make_port_elems_bulk(ports, *proto);
|
|
685
686
|
auto op = std::make_unique<BulkDelPortElemOp>(std::move(elems), config_, *set_idx);
|
|
686
687
|
auto deferred = Napi::Promise::Deferred::New(env);
|
|
687
688
|
auto promise = deferred.Promise();
|
package/src/validation.cpp
CHANGED
|
@@ -4,6 +4,10 @@
|
|
|
4
4
|
#include <cmath>
|
|
5
5
|
#include <cstring>
|
|
6
6
|
|
|
7
|
+
namespace {
|
|
8
|
+
|
|
9
|
+
// Parse IP string, validate via inet_pton, store binary form.
|
|
10
|
+
// Returns IpAddr with family=Invalid on failure.
|
|
7
11
|
IpAddr parse_ip(const std::string& ip) {
|
|
8
12
|
IpAddr result;
|
|
9
13
|
|
|
@@ -26,6 +30,157 @@ IpAddr parse_ip(const std::string& ip) {
|
|
|
26
30
|
return result;
|
|
27
31
|
}
|
|
28
32
|
|
|
33
|
+
// Big-endian increment by 1 with carry propagation.
|
|
34
|
+
void increment_ip(uint8_t* bytes, uint32_t len) {
|
|
35
|
+
for (int i = static_cast<int>(len) - 1; i >= 0; --i) {
|
|
36
|
+
if (++bytes[i] != 0) {
|
|
37
|
+
return; // no carry
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
// Full overflow (e.g., 255.255.255.255 + 1) — bytes wrap to all zeros.
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Compute exclusive end address from network + prefix_len.
|
|
44
|
+
// end = network address with host bits zeroed, then the prefix portion incremented.
|
|
45
|
+
// For /8 on 10.0.0.0: byte[0]=10, increment -> 11, rest stays 0 -> 11.0.0.0
|
|
46
|
+
void compute_cidr_end(const IpAddr& network, uint8_t prefix_len, IpAddr& end) {
|
|
47
|
+
end.family = network.family;
|
|
48
|
+
end.len = network.len;
|
|
49
|
+
|
|
50
|
+
uint32_t full_bytes = prefix_len / 8;
|
|
51
|
+
uint32_t remainder_bits = prefix_len % 8;
|
|
52
|
+
|
|
53
|
+
// Copy prefix bytes
|
|
54
|
+
for (uint32_t i = 0; i < full_bytes && i < network.len; ++i) {
|
|
55
|
+
end.bytes[i] = network.bytes[i];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (remainder_bits > 0 && full_bytes < network.len) {
|
|
59
|
+
// Mask off host bits in the boundary byte
|
|
60
|
+
uint8_t mask = static_cast<uint8_t>(0xFF << (8 - remainder_bits));
|
|
61
|
+
end.bytes[full_bytes] = network.bytes[full_bytes] & mask;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Zero all bytes after the prefix boundary
|
|
65
|
+
uint32_t start_zero = full_bytes + (remainder_bits > 0 ? 1 : 0);
|
|
66
|
+
for (uint32_t i = start_zero; i < network.len; ++i) {
|
|
67
|
+
end.bytes[i] = 0;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Increment the prefix portion to get the exclusive end.
|
|
71
|
+
// We increment at bit position prefix_len. This is equivalent to adding
|
|
72
|
+
// 1 to the (prefix_len)-bit number formed by the first prefix_len bits.
|
|
73
|
+
if (remainder_bits > 0) {
|
|
74
|
+
// The boundary byte has some prefix bits. We need to increment
|
|
75
|
+
// at the bit position. Add (1 << (8 - remainder_bits)) to that byte,
|
|
76
|
+
// with carry propagation into earlier bytes.
|
|
77
|
+
uint8_t add_val = static_cast<uint8_t>(1 << (8 - remainder_bits));
|
|
78
|
+
uint16_t carry = add_val;
|
|
79
|
+
for (int i = static_cast<int>(full_bytes); i >= 0 && carry > 0; --i) {
|
|
80
|
+
carry += end.bytes[i];
|
|
81
|
+
end.bytes[i] = static_cast<uint8_t>(carry & 0xFF);
|
|
82
|
+
carry >>= 8;
|
|
83
|
+
}
|
|
84
|
+
} else {
|
|
85
|
+
// Prefix ends on a byte boundary. Increment the last prefix byte
|
|
86
|
+
// with carry into earlier bytes.
|
|
87
|
+
if (full_bytes > 0) {
|
|
88
|
+
uint16_t carry = 1;
|
|
89
|
+
for (int i = static_cast<int>(full_bytes) - 1; i >= 0 && carry > 0; --i) {
|
|
90
|
+
carry += end.bytes[i];
|
|
91
|
+
end.bytes[i] = static_cast<uint8_t>(carry & 0xFF);
|
|
92
|
+
carry >>= 8;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
} // namespace
|
|
99
|
+
|
|
100
|
+
CidrAddr parse_ip_or_cidr(const std::string& input) {
|
|
101
|
+
CidrAddr result{};
|
|
102
|
+
|
|
103
|
+
auto slash_pos = input.find('/');
|
|
104
|
+
|
|
105
|
+
if (slash_pos == std::string::npos) {
|
|
106
|
+
// Plain IP address
|
|
107
|
+
IpAddr ip = parse_ip(input);
|
|
108
|
+
if (ip.family == IpFamily::Invalid) {
|
|
109
|
+
return result;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
result.network = ip;
|
|
113
|
+
|
|
114
|
+
// end = ip + 1
|
|
115
|
+
result.end = ip;
|
|
116
|
+
increment_ip(result.end.bytes.data(), result.end.len);
|
|
117
|
+
return result;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// CIDR notation: "base/prefix"
|
|
121
|
+
std::string base_str = input.substr(0, slash_pos);
|
|
122
|
+
std::string prefix_str = input.substr(slash_pos + 1);
|
|
123
|
+
|
|
124
|
+
// Reject empty prefix or non-numeric prefix
|
|
125
|
+
if (prefix_str.empty()) {
|
|
126
|
+
return result;
|
|
127
|
+
}
|
|
128
|
+
for (char c : prefix_str) {
|
|
129
|
+
if (c < '0' || c > '9') {
|
|
130
|
+
return result;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Manual parse — no exceptions needed (digits already validated above).
|
|
135
|
+
// Reject prefix strings longer than 3 chars (max valid: "128").
|
|
136
|
+
if (prefix_str.size() > 3) {
|
|
137
|
+
return result;
|
|
138
|
+
}
|
|
139
|
+
int prefix = 0;
|
|
140
|
+
for (char c : prefix_str) {
|
|
141
|
+
prefix = prefix * 10 + (c - '0');
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
IpAddr ip = parse_ip(base_str);
|
|
145
|
+
if (ip.family == IpFamily::Invalid) {
|
|
146
|
+
return result;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
uint8_t max_prefix = (ip.family == IpFamily::IPv4) ? 32 : 128;
|
|
150
|
+
|
|
151
|
+
// Reject /0 (too dangerous) and prefix > max
|
|
152
|
+
if (prefix < 1 || prefix > max_prefix) {
|
|
153
|
+
return result;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
auto prefix_len = static_cast<uint8_t>(prefix);
|
|
157
|
+
|
|
158
|
+
// Verify host bits are zero
|
|
159
|
+
uint32_t full_bytes = prefix_len / 8;
|
|
160
|
+
uint32_t remainder_bits = prefix_len % 8;
|
|
161
|
+
|
|
162
|
+
// Check boundary byte: host bits must be zero
|
|
163
|
+
if (remainder_bits > 0 && full_bytes < ip.len) {
|
|
164
|
+
uint8_t mask = static_cast<uint8_t>(0xFF >> remainder_bits);
|
|
165
|
+
if ((ip.bytes[full_bytes] & mask) != 0) {
|
|
166
|
+
return result; // host bits set in boundary byte
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Check all bytes after the prefix boundary must be zero
|
|
171
|
+
uint32_t start_check = full_bytes + (remainder_bits > 0 ? 1 : 0);
|
|
172
|
+
for (uint32_t i = start_check; i < ip.len; ++i) {
|
|
173
|
+
if (ip.bytes[i] != 0) {
|
|
174
|
+
return result; // host bits set
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
result.network = ip;
|
|
179
|
+
|
|
180
|
+
compute_cidr_end(ip, prefix_len, result.end);
|
|
181
|
+
return result;
|
|
182
|
+
}
|
|
183
|
+
|
|
29
184
|
PortVal parse_port(double value) {
|
|
30
185
|
if (std::isnan(value) || value < 0.0 || value > 65535.0) {
|
|
31
186
|
return {0, false};
|
package/src/validation.h
CHANGED
|
@@ -16,9 +16,18 @@ struct IpAddr {
|
|
|
16
16
|
uint32_t len = 0;
|
|
17
17
|
};
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
struct CidrAddr {
|
|
20
|
+
IpAddr network; // network address (e.g., 10.0.0.0)
|
|
21
|
+
IpAddr end; // exclusive end address (e.g., 11.0.0.0 for /8)
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
// Parse IP string or CIDR notation. Accepts:
|
|
25
|
+
// "1.2.3.4" -> CidrAddr{network=1.2.3.4, end=1.2.3.5}
|
|
26
|
+
// "10.0.0.0/8" -> CidrAddr{network=10.0.0.0, end=11.0.0.0}
|
|
27
|
+
// "2001:db8::/32" -> CidrAddr{network=2001:db8::, end=2001:db9::}
|
|
28
|
+
// Returns CidrAddr with network.family=Invalid on failure.
|
|
29
|
+
// Rejects: host bits set (192.168.1.1/24), prefix > 32/128, /0 (too dangerous).
|
|
30
|
+
[[nodiscard]] CidrAddr parse_ip_or_cidr(const std::string& input);
|
|
22
31
|
|
|
23
32
|
struct PortVal {
|
|
24
33
|
uint16_t port;
|