nftables-napi 0.1.0 → 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.
@@ -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