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