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.
@@ -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
- // First read: blocking (kernel response should arrive promptly)
97
- ssize_t ret = mnl_socket_recvfrom(nl_, recv_buf, sizeof(recv_buf));
98
- if (ret < 0)
99
- return {false, std::string("mnl_socket_recvfrom: ") + strerror(errno)};
100
- while (ret > 0) {
101
- int cb_ret = mnl_cb_run2(recv_buf, static_cast<size_t>(ret), 0, portid_,
102
- nullptr, &ctx, cb_ctl, NLMSG_ERROR + 1);
103
- if (cb_ret <= 0)
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
- // Non-blocking read for remaining kernel responses
107
- ret = recv(fd, recv_buf, sizeof(recv_buf), MSG_DONTWAIT);
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
- // Drain any leftover messages to keep socket clean for next operation
111
- while (recv(fd, recv_buf, sizeof(recv_buf), MSG_DONTWAIT) > 0) {}
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)};
@@ -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_;
@@ -7,5 +7,5 @@ class NlSocket;
7
7
  class NlOperation {
8
8
  public:
9
9
  virtual ~NlOperation() = default;
10
- virtual NlResult execute(NlSocket& sock) = 0;
10
+ [[nodiscard]] virtual NlResult execute(NlSocket& sock) = 0;
11
11
  };
@@ -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
+ }
@@ -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
- nftnl_set_set_u32(s.get(), NFTNL_SET_FLAGS, NFT_SET_TIMEOUT);
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
- // Helper: creates a rule with table/chain/family metadata, calls build_exprs
105
- // to populate expressions, then adds to batch. Returns false on any failure.
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
- static bool add_rule(NlBatch& batch, uint32_t family, const char* table,
125
- const char* chain, const char* set_name,
126
- uint32_t payload_offset, uint32_t addr_len,
127
- const char* log_prefix) {
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
- DATATYPE_IPADDR, IPV4_ADDR_LEN, sid++)
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
- DATATYPE_IP6ADDR, IPV6_ADDR_LEN, sid++))
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
- if (!add_rule(batch, NFPROTO_IPV4, cfg_->table_v4.c_str(), CHAIN_INPUT,
178
- sd.name.c_str(), IPV4_SRC_OFFSET, IPV4_ADDR_LEN, lp)
179
- || !add_rule(batch, NFPROTO_IPV4, cfg_->table_v4.c_str(), CHAIN_FORWARD,
180
- sd.name.c_str(), IPV4_SRC_OFFSET, IPV4_ADDR_LEN, lp)
181
- || !add_rule(batch, NFPROTO_IPV6, cfg_->table_v6.c_str(), CHAIN_INPUT,
182
- sd.name_v6.c_str(), IPV6_SRC_OFFSET, IPV6_ADDR_LEN, lp)
183
- || !add_rule(batch, NFPROTO_IPV6, cfg_->table_v6.c_str(), CHAIN_FORWARD,
184
- sd.name_v6.c_str(), IPV6_SRC_OFFSET, IPV6_ADDR_LEN, lp))
185
- return {false, "failed to build rules for set '" + sd.name + "'"};
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