nftables-napi 0.4.2 → 0.4.4
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 +12 -4
- 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/set_ops.cpp +70 -2
- package/src/netlink/set_ops.h +2 -0
- package/src/validation.cpp +7 -12
- package/src/validation.h +10 -4
package/lib/index.d.ts
CHANGED
|
@@ -31,7 +31,11 @@ export interface NftManagerOptions {
|
|
|
31
31
|
|
|
32
32
|
/** Options for adding a single address. */
|
|
33
33
|
export interface AddAddressOptions {
|
|
34
|
-
/**
|
|
34
|
+
/**
|
|
35
|
+
* IPv4/IPv6 address or CIDR (e.g., "1.2.3.4", "10.0.0.0/8", "2001:db8::/32").
|
|
36
|
+
* Misaligned CIDR (host bits set) is auto-normalized to the network address,
|
|
37
|
+
* e.g. "10.0.0.5/24" becomes "10.0.0.0/24".
|
|
38
|
+
*/
|
|
35
39
|
ip: string;
|
|
36
40
|
/** Target set name (must match one from constructor's ingressAddrSets or egressAddrSets). */
|
|
37
41
|
set: string;
|
|
@@ -41,7 +45,11 @@ export interface AddAddressOptions {
|
|
|
41
45
|
|
|
42
46
|
/** Options for removing a single address. */
|
|
43
47
|
export interface RemoveAddressOptions {
|
|
44
|
-
/**
|
|
48
|
+
/**
|
|
49
|
+
* IPv4/IPv6 address or CIDR to remove (must match exactly as added).
|
|
50
|
+
* Misaligned CIDR (host bits set) is auto-normalized to the network address,
|
|
51
|
+
* e.g. "10.0.0.5/24" becomes "10.0.0.0/24".
|
|
52
|
+
*/
|
|
45
53
|
ip: string;
|
|
46
54
|
/** Target set name (must match one from constructor's ingressAddrSets or egressAddrSets). */
|
|
47
55
|
set: string;
|
|
@@ -49,7 +57,7 @@ export interface RemoveAddressOptions {
|
|
|
49
57
|
|
|
50
58
|
/** Options for bulk adding addresses. */
|
|
51
59
|
export interface AddAddressesOptions {
|
|
52
|
-
/** Array of IPv4/IPv6 addresses or CIDRs. */
|
|
60
|
+
/** Array of IPv4/IPv6 addresses or CIDRs. Misaligned CIDR is auto-normalized. */
|
|
53
61
|
ips: string[];
|
|
54
62
|
/** Target set name (must match one from constructor's ingressAddrSets or egressAddrSets). */
|
|
55
63
|
set: string;
|
|
@@ -59,7 +67,7 @@ export interface AddAddressesOptions {
|
|
|
59
67
|
|
|
60
68
|
/** Options for bulk removing addresses. */
|
|
61
69
|
export interface RemoveAddressesOptions {
|
|
62
|
-
/** Array of IPv4/IPv6 addresses or CIDRs to remove. */
|
|
70
|
+
/** Array of IPv4/IPv6 addresses or CIDRs to remove. Misaligned CIDR is auto-normalized. */
|
|
63
71
|
ips: string[];
|
|
64
72
|
/** Target set name (must match one from constructor's ingressAddrSets or egressAddrSets). */
|
|
65
73
|
set: string;
|
package/package.json
CHANGED
|
Binary file
|
|
Binary file
|
package/src/netlink/set_ops.cpp
CHANGED
|
@@ -12,6 +12,7 @@ extern "C" {
|
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
#include <algorithm>
|
|
15
|
+
#include <cstring>
|
|
15
16
|
|
|
16
17
|
static_assert(nft::FAMILY_IPV4 == NFPROTO_IPV4, "nft::FAMILY_IPV4 must match NFPROTO_IPV4");
|
|
17
18
|
static_assert(nft::FAMILY_IPV6 == NFPROTO_IPV6, "nft::FAMILY_IPV6 must match NFPROTO_IPV6");
|
|
@@ -19,6 +20,71 @@ static_assert(nft::PROTO_SERVICE_KEY_LEN == 8, "concatenated proto.service key m
|
|
|
19
20
|
|
|
20
21
|
enum class SetElemAction { Add, Del };
|
|
21
22
|
|
|
23
|
+
// Merge overlapping or adjacent intervals per family. Required because
|
|
24
|
+
// rbtree-backed interval sets reject inserts that overlap an existing range,
|
|
25
|
+
// and adjacent ranges produce duplicate boundary keys (start of one == end
|
|
26
|
+
// marker of another). Result is sorted, disjoint by family.
|
|
27
|
+
//
|
|
28
|
+
// Wraparound intervals (end_bytes <= bytes lexicographically — e.g. a /32 of
|
|
29
|
+
// 255.255.255.255 whose exclusive end overflows to 0.0.0.0) are not merged:
|
|
30
|
+
// lex-comparison breaks down across the address-space wrap, and naive merge
|
|
31
|
+
// would silently drop the entry. They are passed through as-is.
|
|
32
|
+
static std::vector<ParsedAddr> merge_intervals(std::vector<ParsedAddr> addrs) {
|
|
33
|
+
if (addrs.size() < 2) return addrs;
|
|
34
|
+
|
|
35
|
+
std::sort(addrs.begin(), addrs.end(), [](const ParsedAddr& a, const ParsedAddr& b) {
|
|
36
|
+
if (a.family != b.family) return a.family < b.family;
|
|
37
|
+
return std::memcmp(a.bytes, b.bytes, a.len) < 0;
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
auto is_wrapped = [](const ParsedAddr& p) {
|
|
41
|
+
return std::memcmp(p.end_bytes, p.bytes, p.len) <= 0;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
std::vector<ParsedAddr> merged;
|
|
45
|
+
merged.reserve(addrs.size());
|
|
46
|
+
merged.push_back(addrs[0]);
|
|
47
|
+
|
|
48
|
+
for (size_t i = 1; i < addrs.size(); ++i) {
|
|
49
|
+
ParsedAddr& last = merged.back();
|
|
50
|
+
const ParsedAddr& cur = addrs[i];
|
|
51
|
+
|
|
52
|
+
if (cur.family != last.family || is_wrapped(cur) || is_wrapped(last)) {
|
|
53
|
+
merged.push_back(cur);
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Overlap or adjacency: cur.start <= last.end (end is exclusive).
|
|
58
|
+
if (std::memcmp(cur.bytes, last.end_bytes, cur.len) <= 0) {
|
|
59
|
+
// Extend end if cur reaches further.
|
|
60
|
+
if (std::memcmp(cur.end_bytes, last.end_bytes, cur.len) > 0) {
|
|
61
|
+
std::memcpy(last.end_bytes, cur.end_bytes, cur.len);
|
|
62
|
+
}
|
|
63
|
+
} else {
|
|
64
|
+
merged.push_back(cur);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return merged;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Drop duplicate (proto, port) pairs. Concat sets reject duplicates inside
|
|
72
|
+
// a single batch with EEXIST.
|
|
73
|
+
static std::vector<PortElem> dedup_port_elems(std::vector<PortElem> elems) {
|
|
74
|
+
if (elems.size() < 2) return elems;
|
|
75
|
+
|
|
76
|
+
std::sort(elems.begin(), elems.end(), [](const PortElem& a, const PortElem& b) {
|
|
77
|
+
if (a.proto != b.proto) return a.proto < b.proto;
|
|
78
|
+
return a.port < b.port;
|
|
79
|
+
});
|
|
80
|
+
elems.erase(std::unique(elems.begin(), elems.end(),
|
|
81
|
+
[](const PortElem& a, const PortElem& b) {
|
|
82
|
+
return a.proto == b.proto && a.port == b.port;
|
|
83
|
+
}),
|
|
84
|
+
elems.end());
|
|
85
|
+
return elems;
|
|
86
|
+
}
|
|
87
|
+
|
|
22
88
|
static NlResult bulk_set_elem_op(
|
|
23
89
|
const std::vector<ParsedAddr>& addrs,
|
|
24
90
|
NlSocket& sock,
|
|
@@ -107,7 +173,8 @@ BulkAddSetElemOp::BulkAddSetElemOp(std::vector<ParsedAddr> addrs, uint64_t timeo
|
|
|
107
173
|
cfg_(std::move(config)), set_idx_(set_idx) {}
|
|
108
174
|
|
|
109
175
|
NlResult BulkAddSetElemOp::execute(NlSocket& sock) {
|
|
110
|
-
|
|
176
|
+
auto merged = merge_intervals(std::move(addrs_));
|
|
177
|
+
return bulk_set_elem_op(merged, sock, SetElemAction::Add, *cfg_, set_idx_, timeout_ms_);
|
|
111
178
|
}
|
|
112
179
|
|
|
113
180
|
BulkDelSetElemOp::BulkDelSetElemOp(std::vector<ParsedAddr> addrs,
|
|
@@ -194,7 +261,8 @@ BulkAddPortElemOp::BulkAddPortElemOp(std::vector<PortElem> elems, uint64_t timeo
|
|
|
194
261
|
cfg_(std::move(config)), set_idx_(set_idx) {}
|
|
195
262
|
|
|
196
263
|
NlResult BulkAddPortElemOp::execute(NlSocket& sock) {
|
|
197
|
-
|
|
264
|
+
auto deduped = dedup_port_elems(std::move(elems_));
|
|
265
|
+
return bulk_port_elem_op(deduped, sock, SetElemAction::Add, *cfg_, set_idx_, timeout_ms_);
|
|
198
266
|
}
|
|
199
267
|
|
|
200
268
|
BulkDelPortElemOp::BulkDelPortElemOp(std::vector<PortElem> elems,
|
package/src/netlink/set_ops.h
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
#include <memory>
|
|
8
8
|
#include <vector>
|
|
9
9
|
|
|
10
|
+
// One-shot Op: execute() consumes addrs_ via move; do not call twice.
|
|
10
11
|
class BulkAddSetElemOp final : public NlOperation {
|
|
11
12
|
public:
|
|
12
13
|
BulkAddSetElemOp(std::vector<ParsedAddr> addrs, uint64_t timeout_ms,
|
|
@@ -37,6 +38,7 @@ struct PortElem {
|
|
|
37
38
|
uint16_t port;
|
|
38
39
|
};
|
|
39
40
|
|
|
41
|
+
// One-shot Op: execute() consumes elems_ via move; do not call twice.
|
|
40
42
|
class BulkAddPortElemOp final : public NlOperation {
|
|
41
43
|
public:
|
|
42
44
|
BulkAddPortElemOp(std::vector<PortElem> elems, uint64_t timeout_ms,
|
package/src/validation.cpp
CHANGED
|
@@ -155,24 +155,19 @@ CidrAddr parse_ip_or_cidr(const std::string& input) {
|
|
|
155
155
|
|
|
156
156
|
auto prefix_len = static_cast<uint8_t>(prefix);
|
|
157
157
|
|
|
158
|
-
//
|
|
158
|
+
// Mask off host bits silently — input like "10.0.0.5/24" becomes
|
|
159
|
+
// "10.0.0.0/24", matching `ip route` and Python ipaddress(strict=False).
|
|
159
160
|
uint32_t full_bytes = prefix_len / 8;
|
|
160
161
|
uint32_t remainder_bits = prefix_len % 8;
|
|
161
162
|
|
|
162
|
-
// Check boundary byte: host bits must be zero
|
|
163
163
|
if (remainder_bits > 0 && full_bytes < ip.len) {
|
|
164
|
-
uint8_t mask = static_cast<uint8_t>(0xFF
|
|
165
|
-
|
|
166
|
-
return result; // host bits set in boundary byte
|
|
167
|
-
}
|
|
164
|
+
uint8_t mask = static_cast<uint8_t>(0xFF << (8 - remainder_bits));
|
|
165
|
+
ip.bytes[full_bytes] &= mask;
|
|
168
166
|
}
|
|
169
167
|
|
|
170
|
-
|
|
171
|
-
uint32_t
|
|
172
|
-
|
|
173
|
-
if (ip.bytes[i] != 0) {
|
|
174
|
-
return result; // host bits set
|
|
175
|
-
}
|
|
168
|
+
uint32_t start_zero = full_bytes + (remainder_bits > 0 ? 1 : 0);
|
|
169
|
+
for (uint32_t i = start_zero; i < ip.len; ++i) {
|
|
170
|
+
ip.bytes[i] = 0;
|
|
176
171
|
}
|
|
177
172
|
|
|
178
173
|
result.network = ip;
|
package/src/validation.h
CHANGED
|
@@ -22,11 +22,17 @@ struct CidrAddr {
|
|
|
22
22
|
};
|
|
23
23
|
|
|
24
24
|
// Parse IP string or CIDR notation. Accepts:
|
|
25
|
-
// "1.2.3.4"
|
|
26
|
-
// "10.0.0.0/8"
|
|
27
|
-
// "2001:db8::/32"
|
|
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
|
+
// "198.19.0.0/15" -> CidrAddr{network=198.18.0.0, end=198.20.0.0}
|
|
29
|
+
// (host bits silently masked off — see below)
|
|
30
|
+
//
|
|
31
|
+
// Misaligned CIDR (host bits set) is auto-normalized: host bits are
|
|
32
|
+
// masked off to obtain the network address. Matches `ip route` and
|
|
33
|
+
// Python ipaddress.ip_network(strict=False). Rejects only:
|
|
34
|
+
// /0 (too dangerous), prefix > family bits, malformed input.
|
|
28
35
|
// 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
36
|
[[nodiscard]] CidrAddr parse_ip_or_cidr(const std::string& input);
|
|
31
37
|
|
|
32
38
|
struct PortVal {
|