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/netlink/set_ops.cpp
CHANGED
|
@@ -13,10 +13,9 @@ extern "C" {
|
|
|
13
13
|
|
|
14
14
|
#include <algorithm>
|
|
15
15
|
|
|
16
|
-
using namespace nft;
|
|
17
|
-
|
|
18
16
|
static_assert(nft::FAMILY_IPV4 == NFPROTO_IPV4, "nft::FAMILY_IPV4 must match NFPROTO_IPV4");
|
|
19
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");
|
|
20
19
|
|
|
21
20
|
enum class SetElemAction { Add, Del };
|
|
22
21
|
|
|
@@ -25,10 +24,9 @@ static NlResult bulk_set_elem_op(
|
|
|
25
24
|
NlSocket& sock,
|
|
26
25
|
SetElemAction action,
|
|
27
26
|
const nft::NftConfig& cfg,
|
|
28
|
-
|
|
27
|
+
size_t set_idx,
|
|
29
28
|
uint64_t timeout_ms = 0)
|
|
30
29
|
{
|
|
31
|
-
// Partition by family
|
|
32
30
|
std::vector<const ParsedAddr*> v4, v6;
|
|
33
31
|
for (const auto& a : addrs) {
|
|
34
32
|
if (a.family == NFPROTO_IPV4) v4.push_back(&a);
|
|
@@ -41,20 +39,20 @@ static NlResult bulk_set_elem_op(
|
|
|
41
39
|
? (NLM_F_CREATE | NLM_F_ACK) : NLM_F_ACK;
|
|
42
40
|
const bool ignore_enoent = (action == SetElemAction::Del);
|
|
43
41
|
|
|
44
|
-
|
|
42
|
+
const auto& sd = cfg.sets[set_idx];
|
|
43
|
+
|
|
45
44
|
auto process = [&](uint32_t family, const std::vector<const ParsedAddr*>& family_addrs) -> NlResult {
|
|
46
45
|
if (family_addrs.empty()) return {true, ""};
|
|
47
46
|
|
|
48
47
|
const char* table = (family == NFPROTO_IPV4)
|
|
49
48
|
? cfg.table_v4.c_str() : cfg.table_v6.c_str();
|
|
50
49
|
const char* set_name = (family == NFPROTO_IPV4)
|
|
51
|
-
?
|
|
52
|
-
|
|
53
|
-
uint32_t
|
|
54
|
-
uint32_t key_len = (family == NFPROTO_IPV4) ? IPV4_ADDR_LEN : IPV6_ADDR_LEN;
|
|
50
|
+
? sd.name.c_str() : sd.name_v6.c_str();
|
|
51
|
+
uint32_t key_type = (family == NFPROTO_IPV4) ? nft::DATATYPE_IPADDR : nft::DATATYPE_IP6ADDR;
|
|
52
|
+
uint32_t key_len = (family == NFPROTO_IPV4) ? nft::IPV4_ADDR_LEN : nft::IPV6_ADDR_LEN;
|
|
55
53
|
|
|
56
|
-
for (size_t offset = 0; offset < family_addrs.size(); offset += BULK_CHUNK_SIZE) {
|
|
57
|
-
size_t end = std::min(offset + static_cast<size_t>(BULK_CHUNK_SIZE), family_addrs.size());
|
|
54
|
+
for (size_t offset = 0; offset < family_addrs.size(); offset += nft::BULK_CHUNK_SIZE) {
|
|
55
|
+
size_t end = std::min(offset + static_cast<size_t>(nft::BULK_CHUNK_SIZE), family_addrs.size());
|
|
58
56
|
|
|
59
57
|
auto s = nft::make_set();
|
|
60
58
|
if (!s) return {false, "nftnl_set_alloc failed"};
|
|
@@ -72,7 +70,7 @@ static NlResult bulk_set_elem_op(
|
|
|
72
70
|
if (timeout_ms > 0) {
|
|
73
71
|
nftnl_set_elem_set_u64(e, NFTNL_SET_ELEM_TIMEOUT, timeout_ms);
|
|
74
72
|
}
|
|
75
|
-
nftnl_set_elem_add(s.get(), e);
|
|
73
|
+
nftnl_set_elem_add(s.get(), e);
|
|
76
74
|
}
|
|
77
75
|
|
|
78
76
|
NlBatch batch;
|
|
@@ -96,18 +94,105 @@ static NlResult bulk_set_elem_op(
|
|
|
96
94
|
}
|
|
97
95
|
|
|
98
96
|
BulkAddSetElemOp::BulkAddSetElemOp(std::vector<ParsedAddr> addrs, uint64_t timeout_ms,
|
|
99
|
-
std::shared_ptr<const nft::NftConfig> config,
|
|
97
|
+
std::shared_ptr<const nft::NftConfig> config, size_t set_idx)
|
|
100
98
|
: addrs_(std::move(addrs)), timeout_ms_(timeout_ms),
|
|
101
|
-
cfg_(std::move(config)),
|
|
99
|
+
cfg_(std::move(config)), set_idx_(set_idx) {}
|
|
102
100
|
|
|
103
101
|
NlResult BulkAddSetElemOp::execute(NlSocket& sock) {
|
|
104
|
-
return bulk_set_elem_op(addrs_, sock, SetElemAction::Add, *cfg_,
|
|
102
|
+
return bulk_set_elem_op(addrs_, sock, SetElemAction::Add, *cfg_, set_idx_, timeout_ms_);
|
|
105
103
|
}
|
|
106
104
|
|
|
107
105
|
BulkDelSetElemOp::BulkDelSetElemOp(std::vector<ParsedAddr> addrs,
|
|
108
|
-
std::shared_ptr<const nft::NftConfig> config,
|
|
109
|
-
: addrs_(std::move(addrs)), cfg_(std::move(config)),
|
|
106
|
+
std::shared_ptr<const nft::NftConfig> config, size_t set_idx)
|
|
107
|
+
: addrs_(std::move(addrs)), cfg_(std::move(config)), set_idx_(set_idx) {}
|
|
110
108
|
|
|
111
109
|
NlResult BulkDelSetElemOp::execute(NlSocket& sock) {
|
|
112
|
-
return bulk_set_elem_op(addrs_, sock, SetElemAction::Del, *cfg_,
|
|
110
|
+
return bulk_set_elem_op(addrs_, sock, SetElemAction::Del, *cfg_, set_idx_);
|
|
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_);
|
|
113
198
|
}
|
package/src/netlink/set_ops.h
CHANGED
|
@@ -3,30 +3,61 @@
|
|
|
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
|
|
|
9
10
|
class BulkAddSetElemOp final : public NlOperation {
|
|
10
11
|
public:
|
|
11
12
|
BulkAddSetElemOp(std::vector<ParsedAddr> addrs, uint64_t timeout_ms,
|
|
12
|
-
std::shared_ptr<const nft::NftConfig> config,
|
|
13
|
+
std::shared_ptr<const nft::NftConfig> config, size_t set_idx);
|
|
13
14
|
NlResult execute(NlSocket& sock) override;
|
|
14
15
|
|
|
15
16
|
private:
|
|
16
17
|
std::vector<ParsedAddr> addrs_;
|
|
17
18
|
uint64_t timeout_ms_;
|
|
18
19
|
std::shared_ptr<const nft::NftConfig> cfg_;
|
|
19
|
-
|
|
20
|
+
size_t set_idx_;
|
|
20
21
|
};
|
|
21
22
|
|
|
22
23
|
class BulkDelSetElemOp final : public NlOperation {
|
|
23
24
|
public:
|
|
24
25
|
BulkDelSetElemOp(std::vector<ParsedAddr> addrs,
|
|
25
|
-
std::shared_ptr<const nft::NftConfig> config,
|
|
26
|
+
std::shared_ptr<const nft::NftConfig> config, size_t set_idx);
|
|
26
27
|
NlResult execute(NlSocket& sock) override;
|
|
27
28
|
|
|
28
29
|
private:
|
|
29
30
|
std::vector<ParsedAddr> addrs_;
|
|
30
31
|
std::shared_ptr<const nft::NftConfig> cfg_;
|
|
31
|
-
|
|
32
|
+
size_t set_idx_;
|
|
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_;
|
|
32
63
|
};
|
|
@@ -11,12 +11,15 @@ extern "C" {
|
|
|
11
11
|
#include <libnftnl/set.h>
|
|
12
12
|
#include <libnftnl/rule.h>
|
|
13
13
|
#include <libnftnl/expr.h>
|
|
14
|
+
#include <libnftnl/object.h>
|
|
14
15
|
#include <linux/netfilter.h>
|
|
15
16
|
#include <linux/netfilter/nf_tables.h>
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
using namespace nft;
|
|
19
20
|
|
|
21
|
+
// ── Table helpers ────────────────────────────────────────────────────────────
|
|
22
|
+
|
|
20
23
|
static bool add_table(NlBatch& batch, uint32_t family, const char* name,
|
|
21
24
|
uint16_t msg_type, uint16_t extra_flags) {
|
|
22
25
|
auto t = nft::make_table();
|
|
@@ -29,6 +32,8 @@ static bool add_table(NlBatch& batch, uint32_t family, const char* name,
|
|
|
29
32
|
return batch.advance();
|
|
30
33
|
}
|
|
31
34
|
|
|
35
|
+
// ── Chain helpers ────────────────────────────────────────────────────────────
|
|
36
|
+
|
|
32
37
|
static bool add_chain(NlBatch& batch, uint32_t family, const char* table,
|
|
33
38
|
const char* name, uint32_t hooknum) {
|
|
34
39
|
auto c = nft::make_chain();
|
|
@@ -45,9 +50,28 @@ static bool add_chain(NlBatch& batch, uint32_t family, const char* table,
|
|
|
45
50
|
return batch.advance();
|
|
46
51
|
}
|
|
47
52
|
|
|
53
|
+
// ── Named counter (stateful object) ─────────────────────────────────────────
|
|
54
|
+
|
|
55
|
+
static bool add_counter_obj(NlBatch& batch, uint32_t family, const char* table,
|
|
56
|
+
const char* name) {
|
|
57
|
+
auto o = nft::make_obj();
|
|
58
|
+
if (!o) return false;
|
|
59
|
+
nftnl_obj_set_str(o.get(), NFTNL_OBJ_TABLE, table);
|
|
60
|
+
nftnl_obj_set_str(o.get(), NFTNL_OBJ_NAME, name);
|
|
61
|
+
nftnl_obj_set_u32(o.get(), NFTNL_OBJ_TYPE, NFT_OBJECT_COUNTER);
|
|
62
|
+
struct nlmsghdr* nlh = batch.add_msg(NFT_MSG_NEWOBJ, family, NLM_F_CREATE | NLM_F_ACK);
|
|
63
|
+
if (!nlh) return false;
|
|
64
|
+
nftnl_obj_nlmsg_build_payload(nlh, o.get());
|
|
65
|
+
return batch.advance();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ── Set helpers ─────────────────────────────────────────────────────────────
|
|
69
|
+
|
|
48
70
|
static bool add_set(NlBatch& batch, uint32_t family, const char* table,
|
|
49
71
|
const char* name, uint32_t key_type, uint32_t key_len,
|
|
50
|
-
uint32_t set_id
|
|
72
|
+
uint32_t set_id,
|
|
73
|
+
const uint8_t* concat_field_lens = nullptr,
|
|
74
|
+
size_t concat_field_count = 0) {
|
|
51
75
|
auto s = nft::make_set();
|
|
52
76
|
if (!s) return false;
|
|
53
77
|
nftnl_set_set_str(s.get(), NFTNL_SET_TABLE, table);
|
|
@@ -55,14 +79,31 @@ static bool add_set(NlBatch& batch, uint32_t family, const char* table,
|
|
|
55
79
|
nftnl_set_set_u32(s.get(), NFTNL_SET_FAMILY, family);
|
|
56
80
|
nftnl_set_set_u32(s.get(), NFTNL_SET_KEY_TYPE, key_type);
|
|
57
81
|
nftnl_set_set_u32(s.get(), NFTNL_SET_KEY_LEN, key_len);
|
|
58
|
-
|
|
82
|
+
|
|
83
|
+
uint32_t set_flags = NFT_SET_TIMEOUT | NFT_SET_EXPR;
|
|
84
|
+
if (concat_field_lens && concat_field_count > 0)
|
|
85
|
+
set_flags |= NFT_SET_CONCAT;
|
|
86
|
+
nftnl_set_set_u32(s.get(), NFTNL_SET_FLAGS, set_flags);
|
|
59
87
|
nftnl_set_set_u32(s.get(), NFTNL_SET_ID, set_id);
|
|
88
|
+
|
|
89
|
+
if (concat_field_lens && concat_field_count > 0) {
|
|
90
|
+
nftnl_set_set_data(s.get(), NFTNL_SET_DESC_CONCAT,
|
|
91
|
+
concat_field_lens, concat_field_count);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Per-element counter expression
|
|
95
|
+
struct nftnl_expr* counter = nftnl_expr_alloc("counter");
|
|
96
|
+
if (!counter) return false;
|
|
97
|
+
nftnl_set_add_expr(s.get(), counter); // ownership transferred
|
|
98
|
+
|
|
60
99
|
struct nlmsghdr* nlh = batch.add_msg(NFT_MSG_NEWSET, family, NLM_F_CREATE | NLM_F_ACK);
|
|
61
100
|
if (!nlh) return false;
|
|
62
101
|
nftnl_set_nlmsg_build_payload(nlh, s.get());
|
|
63
102
|
return batch.advance();
|
|
64
103
|
}
|
|
65
104
|
|
|
105
|
+
// ── Expression helpers ──────────────────────────────────────────────────────
|
|
106
|
+
|
|
66
107
|
static bool add_expr_payload(struct nftnl_rule* r, uint32_t base, uint32_t dreg,
|
|
67
108
|
uint32_t offset, uint32_t len) {
|
|
68
109
|
struct nftnl_expr* e = nftnl_expr_alloc("payload");
|
|
@@ -92,6 +133,15 @@ static bool add_expr_log(struct nftnl_rule* r, const char* prefix) {
|
|
|
92
133
|
return true;
|
|
93
134
|
}
|
|
94
135
|
|
|
136
|
+
static bool add_expr_counter_ref(struct nftnl_rule* r, const char* counter_name) {
|
|
137
|
+
struct nftnl_expr* e = nftnl_expr_alloc("objref");
|
|
138
|
+
if (!e) return false;
|
|
139
|
+
nftnl_expr_set_u32(e, NFTNL_EXPR_OBJREF_IMM_TYPE, NFT_OBJECT_COUNTER);
|
|
140
|
+
nftnl_expr_set_str(e, NFTNL_EXPR_OBJREF_IMM_NAME, counter_name);
|
|
141
|
+
nftnl_rule_add_expr(r, e);
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
|
|
95
145
|
static bool add_expr_drop(struct nftnl_rule* r) {
|
|
96
146
|
struct nftnl_expr* e = nftnl_expr_alloc("immediate");
|
|
97
147
|
if (!e) return false;
|
|
@@ -101,8 +151,8 @@ static bool add_expr_drop(struct nftnl_rule* r) {
|
|
|
101
151
|
return true;
|
|
102
152
|
}
|
|
103
153
|
|
|
104
|
-
//
|
|
105
|
-
|
|
154
|
+
// ── Rule builder ────────────────────────────────────────────────────────────
|
|
155
|
+
|
|
106
156
|
template<typename F>
|
|
107
157
|
static bool add_rule_with(NlBatch& batch, uint32_t family, const char* table,
|
|
108
158
|
const char* chain, F build_exprs) {
|
|
@@ -121,22 +171,77 @@ static bool add_rule_with(NlBatch& batch, uint32_t family, const char* table,
|
|
|
121
171
|
return batch.advance();
|
|
122
172
|
}
|
|
123
173
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
174
|
+
// Rule: counter name "processed" (standalone rule in chain)
|
|
175
|
+
static bool add_rule_counter_ref(NlBatch& batch, uint32_t family, const char* table,
|
|
176
|
+
const char* chain) {
|
|
177
|
+
return add_rule_with(batch, family, table, chain, [](nftnl_rule* r) {
|
|
178
|
+
return add_expr_counter_ref(r, COUNTER_NAME);
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// InIP rule: payload(saddr) + lookup(set) + log(prefix) + counter_ref(set_name) + drop
|
|
183
|
+
static bool add_rule_in_ip(NlBatch& batch, uint32_t family, const char* table,
|
|
184
|
+
const char* chain, const char* set_name,
|
|
185
|
+
uint32_t payload_offset, uint32_t addr_len,
|
|
186
|
+
const char* log_prefix, const char* counter_name) {
|
|
128
187
|
return add_rule_with(batch, family, table, chain, [&](nftnl_rule* r) {
|
|
129
188
|
return add_expr_payload(r, NFT_PAYLOAD_NETWORK_HEADER, NFT_REG_1, payload_offset, addr_len)
|
|
130
189
|
&& add_expr_lookup(r, set_name, NFT_REG_1)
|
|
131
190
|
&& add_expr_log(r, log_prefix)
|
|
191
|
+
&& add_expr_counter_ref(r, counter_name)
|
|
132
192
|
&& add_expr_drop(r);
|
|
133
193
|
});
|
|
134
194
|
}
|
|
135
195
|
|
|
196
|
+
// OutIP rule: payload(daddr) + lookup(set) + counter_ref(set_name) + drop (NO log)
|
|
197
|
+
static bool add_rule_out_ip(NlBatch& batch, uint32_t family, const char* table,
|
|
198
|
+
const char* chain, const char* set_name,
|
|
199
|
+
uint32_t payload_offset, uint32_t addr_len,
|
|
200
|
+
const char* counter_name) {
|
|
201
|
+
return add_rule_with(batch, family, table, chain, [&](nftnl_rule* r) {
|
|
202
|
+
return add_expr_payload(r, NFT_PAYLOAD_NETWORK_HEADER, NFT_REG_1, payload_offset, addr_len)
|
|
203
|
+
&& add_expr_lookup(r, set_name, NFT_REG_1)
|
|
204
|
+
&& add_expr_counter_ref(r, counter_name)
|
|
205
|
+
&& add_expr_drop(r);
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// OutPort rule: meta(l4proto) → REG32_00, payload(transport dport) → REG32_01,
|
|
210
|
+
// lookup(concat set, REG32_00) + counter_ref + drop
|
|
211
|
+
static bool add_rule_out_port_concat(NlBatch& batch, uint32_t family, const char* table,
|
|
212
|
+
const char* chain, const char* set_name,
|
|
213
|
+
const char* counter_name) {
|
|
214
|
+
return add_rule_with(batch, family, table, chain, [&](nftnl_rule* r) {
|
|
215
|
+
// meta l4proto → NFT_REG32_00
|
|
216
|
+
struct nftnl_expr* meta = nftnl_expr_alloc("meta");
|
|
217
|
+
if (!meta) return false;
|
|
218
|
+
nftnl_expr_set_u32(meta, NFTNL_EXPR_META_KEY, NFT_META_L4PROTO);
|
|
219
|
+
nftnl_expr_set_u32(meta, NFTNL_EXPR_META_DREG, NFT_REG32_00);
|
|
220
|
+
nftnl_rule_add_expr(r, meta);
|
|
221
|
+
|
|
222
|
+
// payload transport dport → NFT_REG32_01
|
|
223
|
+
struct nftnl_expr* pay = nftnl_expr_alloc("payload");
|
|
224
|
+
if (!pay) return false;
|
|
225
|
+
nftnl_expr_set_u32(pay, NFTNL_EXPR_PAYLOAD_BASE, NFT_PAYLOAD_TRANSPORT_HEADER);
|
|
226
|
+
nftnl_expr_set_u32(pay, NFTNL_EXPR_PAYLOAD_DREG, NFT_REG32_01);
|
|
227
|
+
nftnl_expr_set_u32(pay, NFTNL_EXPR_PAYLOAD_OFFSET, TRANSPORT_DPORT_OFFSET);
|
|
228
|
+
nftnl_expr_set_u32(pay, NFTNL_EXPR_PAYLOAD_LEN, TRANSPORT_DPORT_LEN);
|
|
229
|
+
nftnl_rule_add_expr(r, pay);
|
|
230
|
+
|
|
231
|
+
// lookup in concatenated set starting from REG32_00
|
|
232
|
+
return add_expr_lookup(r, set_name, NFT_REG32_00)
|
|
233
|
+
&& add_expr_counter_ref(r, counter_name)
|
|
234
|
+
&& add_expr_drop(r);
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// ── CreateTableOp ───────────────────────────────────────────────────────────
|
|
239
|
+
|
|
136
240
|
CreateTableOp::CreateTableOp(std::shared_ptr<const nft::NftConfig> config)
|
|
137
241
|
: cfg_(std::move(config)) {}
|
|
138
242
|
|
|
139
243
|
NlResult CreateTableOp::execute(NlSocket& sock) {
|
|
244
|
+
// Phase 1: Delete existing tables (idempotent)
|
|
140
245
|
{
|
|
141
246
|
NlBatch batch;
|
|
142
247
|
if (!batch.is_valid())
|
|
@@ -148,65 +253,117 @@ NlResult CreateTableOp::execute(NlSocket& sock) {
|
|
|
148
253
|
if (!res.success) return res;
|
|
149
254
|
}
|
|
150
255
|
|
|
256
|
+
// Phase 2: Create everything
|
|
151
257
|
NlBatch batch;
|
|
152
258
|
if (!batch.is_valid())
|
|
153
259
|
return {false, "failed to allocate batch"};
|
|
154
260
|
|
|
155
261
|
uint32_t sid = 0;
|
|
156
262
|
|
|
263
|
+
// Tables
|
|
157
264
|
if (!add_table(batch, NFPROTO_IPV4, cfg_->table_v4.c_str(), NFT_MSG_NEWTABLE, NLM_F_CREATE)
|
|
158
265
|
|| !add_table(batch, NFPROTO_IPV6, cfg_->table_v6.c_str(), NFT_MSG_NEWTABLE, NLM_F_CREATE))
|
|
159
266
|
return {false, "failed to build tables"};
|
|
160
267
|
|
|
161
|
-
//
|
|
162
|
-
if (!
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
268
|
+
// Named counter: "processed" (global traffic counter)
|
|
269
|
+
if (!add_counter_obj(batch, NFPROTO_IPV4, cfg_->table_v4.c_str(), COUNTER_NAME)
|
|
270
|
+
|| !add_counter_obj(batch, NFPROTO_IPV6, cfg_->table_v6.c_str(), COUNTER_NAME))
|
|
271
|
+
return {false, "failed to build 'processed' counter"};
|
|
272
|
+
|
|
273
|
+
// Named counters: per-set
|
|
274
|
+
for (const auto& sd : cfg_->sets) {
|
|
275
|
+
if (!add_counter_obj(batch, NFPROTO_IPV4, cfg_->table_v4.c_str(), sd.name.c_str())
|
|
276
|
+
|| !add_counter_obj(batch, NFPROTO_IPV6, cfg_->table_v6.c_str(), sd.name_v6.c_str()))
|
|
277
|
+
return {false, "failed to build counter for set '" + sd.name + "'"};
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Sets
|
|
281
|
+
for (const auto& sd : cfg_->sets) {
|
|
282
|
+
uint32_t key_type_v4, key_type_v6, key_len_v4, key_len_v6;
|
|
283
|
+
const uint8_t* concat_fields = nullptr;
|
|
284
|
+
size_t concat_count = 0;
|
|
285
|
+
static constexpr uint8_t proto_port_fields[2] = {1, 2};
|
|
167
286
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
287
|
+
if (sd.kind == SetKind::OutPort) {
|
|
288
|
+
key_type_v4 = DATATYPE_PROTO_SERVICE;
|
|
289
|
+
key_type_v6 = DATATYPE_PROTO_SERVICE;
|
|
290
|
+
key_len_v4 = PROTO_SERVICE_KEY_LEN;
|
|
291
|
+
key_len_v6 = PROTO_SERVICE_KEY_LEN;
|
|
292
|
+
concat_fields = proto_port_fields;
|
|
293
|
+
concat_count = sizeof(proto_port_fields);
|
|
294
|
+
} else {
|
|
295
|
+
key_type_v4 = DATATYPE_IPADDR;
|
|
296
|
+
key_type_v6 = DATATYPE_IP6ADDR;
|
|
297
|
+
key_len_v4 = IPV4_ADDR_LEN;
|
|
298
|
+
key_len_v6 = IPV6_ADDR_LEN;
|
|
299
|
+
}
|
|
300
|
+
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)
|
|
302
|
+
|| !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))
|
|
304
|
+
return {false, "failed to build set '" + sd.name + "'"};
|
|
305
|
+
}
|
|
174
306
|
|
|
307
|
+
// Chains: input + forward + output
|
|
175
308
|
if (!add_chain(batch, NFPROTO_IPV4, cfg_->table_v4.c_str(), CHAIN_INPUT, NF_INET_LOCAL_IN)
|
|
176
309
|
|| !add_chain(batch, NFPROTO_IPV4, cfg_->table_v4.c_str(), CHAIN_FORWARD, NF_INET_FORWARD)
|
|
310
|
+
|| !add_chain(batch, NFPROTO_IPV4, cfg_->table_v4.c_str(), CHAIN_OUTPUT, NF_INET_LOCAL_OUT)
|
|
177
311
|
|| !add_chain(batch, NFPROTO_IPV6, cfg_->table_v6.c_str(), CHAIN_INPUT, NF_INET_LOCAL_IN)
|
|
178
|
-
|| !add_chain(batch, NFPROTO_IPV6, cfg_->table_v6.c_str(), CHAIN_FORWARD, NF_INET_FORWARD)
|
|
312
|
+
|| !add_chain(batch, NFPROTO_IPV6, cfg_->table_v6.c_str(), CHAIN_FORWARD, NF_INET_FORWARD)
|
|
313
|
+
|| !add_chain(batch, NFPROTO_IPV6, cfg_->table_v6.c_str(), CHAIN_OUTPUT, NF_INET_LOCAL_OUT))
|
|
179
314
|
return {false, "failed to build chains"};
|
|
180
315
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
316
|
+
// Rules: "counter name processed" on input + forward chains
|
|
317
|
+
if (!add_rule_counter_ref(batch, NFPROTO_IPV4, cfg_->table_v4.c_str(), CHAIN_INPUT)
|
|
318
|
+
|| !add_rule_counter_ref(batch, NFPROTO_IPV4, cfg_->table_v4.c_str(), CHAIN_FORWARD)
|
|
319
|
+
|| !add_rule_counter_ref(batch, NFPROTO_IPV6, cfg_->table_v6.c_str(), CHAIN_INPUT)
|
|
320
|
+
|| !add_rule_counter_ref(batch, NFPROTO_IPV6, cfg_->table_v6.c_str(), CHAIN_FORWARD))
|
|
321
|
+
return {false, "failed to build 'processed' counter rules"};
|
|
322
|
+
|
|
323
|
+
// Rules per set
|
|
324
|
+
for (const auto& sd : cfg_->sets) {
|
|
325
|
+
const char* lp = sd.log_prefix.c_str();
|
|
326
|
+
switch (sd.kind) {
|
|
327
|
+
case SetKind::InIP:
|
|
328
|
+
// input + forward: saddr + log + counter_ref + drop
|
|
329
|
+
if (!add_rule_in_ip(batch, NFPROTO_IPV4, cfg_->table_v4.c_str(), CHAIN_INPUT,
|
|
330
|
+
sd.name.c_str(), IPV4_SRC_OFFSET, IPV4_ADDR_LEN, lp, sd.name.c_str())
|
|
331
|
+
|| !add_rule_in_ip(batch, NFPROTO_IPV4, cfg_->table_v4.c_str(), CHAIN_FORWARD,
|
|
332
|
+
sd.name.c_str(), IPV4_SRC_OFFSET, IPV4_ADDR_LEN, lp, sd.name.c_str())
|
|
333
|
+
|| !add_rule_in_ip(batch, NFPROTO_IPV6, cfg_->table_v6.c_str(), CHAIN_INPUT,
|
|
334
|
+
sd.name_v6.c_str(), IPV6_SRC_OFFSET, IPV6_ADDR_LEN, lp, sd.name_v6.c_str())
|
|
335
|
+
|| !add_rule_in_ip(batch, NFPROTO_IPV6, cfg_->table_v6.c_str(), CHAIN_FORWARD,
|
|
336
|
+
sd.name_v6.c_str(), IPV6_SRC_OFFSET, IPV6_ADDR_LEN, lp, sd.name_v6.c_str()))
|
|
337
|
+
return {false, "failed to build rules for set '" + sd.name + "'"};
|
|
338
|
+
break;
|
|
339
|
+
|
|
340
|
+
case SetKind::OutIP:
|
|
341
|
+
// output: daddr + counter_ref + drop (NO log)
|
|
342
|
+
if (!add_rule_out_ip(batch, NFPROTO_IPV4, cfg_->table_v4.c_str(), CHAIN_OUTPUT,
|
|
343
|
+
sd.name.c_str(), IPV4_DST_OFFSET, IPV4_ADDR_LEN,
|
|
344
|
+
sd.name.c_str())
|
|
345
|
+
|| !add_rule_out_ip(batch, NFPROTO_IPV6, cfg_->table_v6.c_str(), CHAIN_OUTPUT,
|
|
346
|
+
sd.name_v6.c_str(), IPV6_DST_OFFSET, IPV6_ADDR_LEN,
|
|
347
|
+
sd.name_v6.c_str()))
|
|
348
|
+
return {false, "failed to build output rules for set '" + sd.name + "'"};
|
|
349
|
+
break;
|
|
350
|
+
|
|
351
|
+
case SetKind::OutPort:
|
|
352
|
+
// output: single concatenated (proto . port) lookup + counter_ref + drop
|
|
353
|
+
if (!add_rule_out_port_concat(batch, NFPROTO_IPV4, cfg_->table_v4.c_str(), CHAIN_OUTPUT,
|
|
354
|
+
sd.name.c_str(), sd.name.c_str())
|
|
355
|
+
|| !add_rule_out_port_concat(batch, NFPROTO_IPV6, cfg_->table_v6.c_str(), CHAIN_OUTPUT,
|
|
356
|
+
sd.name_v6.c_str(), sd.name_v6.c_str()))
|
|
357
|
+
return {false, "failed to build port rules for set '" + sd.name + "'"};
|
|
358
|
+
break;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
206
361
|
|
|
207
362
|
return batch.execute(sock);
|
|
208
363
|
}
|
|
209
364
|
|
|
365
|
+
// ── DeleteTableOp ───────────────────────────────────────────────────────────
|
|
366
|
+
|
|
210
367
|
DeleteTableOp::DeleteTableOp(std::shared_ptr<const nft::NftConfig> config)
|
|
211
368
|
: cfg_(std::move(config)) {}
|
|
212
369
|
|