@push.rocks/smartnftables 4.3.1 → 4.5.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,13 +6,26 @@
6
6
  //! rules prove through `private_link`), the protocols, the live and originally
7
7
  //! tracked tuple and the generation's conntrack zone. Generation labels live in
8
8
  //! two exact sets, so a label alone never authorizes a changed grant.
9
- use super::keys::{self, Key, Loads, ADDRESS, INDEX, LABEL, PORT, PROTOCOL, ZONE};
9
+ use super::keys::{
10
+ self, Field, Key, Loads, CT_LABEL, CT_ORIGINAL_DADDR, CT_ORIGINAL_DPORT, CT_ORIGINAL_SADDR,
11
+ CT_ORIGINAL_SPORT, CT_PROTOCOL, CT_ZONE, IIF, IP_DADDR, IP_SADDR, L4PROTO, TH_DPORT, TH_SPORT,
12
+ };
10
13
  use super::private::known;
11
14
  use super::*;
12
15
 
13
16
  const LABEL_SET: &str = "leased_label";
14
17
  const GENERATION_MAP: &str = "leased_generation";
15
- const RETURN: [u32; 5] = [PROTOCOL, ADDRESS, ADDRESS, PORT, PORT];
18
+ const LABEL_KEY: [Field; 2] = [CT_ZONE, CT_LABEL];
19
+ /// The generation map's key is the zone padded to its register, as every
20
+ /// release before typed sets declared it.
21
+ const GENERATION_KEY: [Field; 1] = [CT_ZONE];
22
+ const GENERATION_DATA: [Field; 1] = [CT_LABEL];
23
+ const RETURN: [Field; 5] = [L4PROTO, IP_SADDR, IP_DADDR, TH_SPORT, TH_DPORT];
24
+ /// The zone a classification stores.
25
+ const ZONE_DATA: [Field; 1] = [CT_ZONE];
26
+ /// The source NAT a flow map stores: the transit address and the first and
27
+ /// last port of the leased range.
28
+ const TRANSLATION: [Field; 3] = [IP_SADDR, TH_SPORT, TH_SPORT];
16
29
 
17
30
  /// Platform grants precede the protected barrier and public grants follow it.
18
31
  #[derive(Clone, Copy, PartialEq, Eq)]
@@ -69,24 +82,26 @@ impl Shape {
69
82
  }
70
83
  /// A source port field only where the grants name one, then the
71
84
  /// destination port.
72
- fn ports(self) -> Vec<u32> {
85
+ fn ports(self, source: Field, destination: Field) -> Vec<Field> {
73
86
  if self.sourced {
74
- vec![PORT, PORT]
87
+ vec![source, destination]
75
88
  } else {
76
- vec![PORT]
89
+ vec![destination]
77
90
  }
78
91
  }
79
- fn classification(self) -> Vec<u32> {
80
- let mut fields = vec![INDEX, PROTOCOL, ADDRESS, ADDRESS];
81
- fields.extend(self.ports());
92
+ /// The classification key, as the incoming direction loads it.
93
+ fn classification(self) -> Vec<Field> {
94
+ let mut fields = vec![IIF, L4PROTO, IP_SADDR, IP_DADDR];
95
+ fields.extend(self.ports(TH_SPORT, TH_DPORT));
82
96
  fields
83
97
  }
84
- fn flow(self) -> Vec<u32> {
85
- let mut fields = vec![INDEX, PROTOCOL, PROTOCOL, ADDRESS, ADDRESS];
86
- fields.extend(self.ports());
87
- fields.extend([ADDRESS, ADDRESS]);
88
- fields.extend(self.ports());
89
- fields.push(ZONE);
98
+ /// The flow key, as the original direction loads it.
99
+ fn flow(self) -> Vec<Field> {
100
+ let mut fields = vec![IIF, L4PROTO, CT_PROTOCOL, IP_SADDR, IP_DADDR];
101
+ fields.extend(self.ports(TH_SPORT, TH_DPORT));
102
+ fields.extend([CT_ORIGINAL_SADDR, CT_ORIGINAL_DADDR]);
103
+ fields.extend(self.ports(CT_ORIGINAL_SPORT, CT_ORIGINAL_DPORT));
104
+ fields.push(CT_ZONE);
90
105
  fields
91
106
  }
92
107
  fn key_ports(self, key: Key, grant: &Grant) -> Key {
@@ -151,8 +166,15 @@ pub(super) fn sets(program: &mut Program<'_>, scope: &RouterScope) -> Result<()>
151
166
  labels.push((key, None));
152
167
  generations.push((zone_word(generation.conntrack_zone), Some(label)));
153
168
  }
154
- keys::exact(program, LABEL_SET, 38, 4 + LABEL, None, labels)?;
155
- keys::exact(program, GENERATION_MAP, 39, 4, Some(LABEL), generations)?;
169
+ keys::exact(program, LABEL_SET, 38, &LABEL_KEY, None, labels)?;
170
+ keys::exact(
171
+ program,
172
+ GENERATION_MAP,
173
+ 39,
174
+ &GENERATION_KEY,
175
+ Some(&GENERATION_DATA),
176
+ generations,
177
+ )?;
156
178
  for kind in [Kind::Platform, Kind::Public] {
157
179
  let mut returns = Vec::new();
158
180
  for shape in Shape::all(kind) {
@@ -212,7 +234,7 @@ pub(super) fn sets(program: &mut Program<'_>, scope: &RouterScope) -> Result<()>
212
234
  &shape.name("zone"),
213
235
  shape.id(0),
214
236
  &shape.classification(),
215
- Some(ZONE),
237
+ Some(&ZONE_DATA),
216
238
  classification,
217
239
  )?;
218
240
  keys::interval(
@@ -220,12 +242,19 @@ pub(super) fn sets(program: &mut Program<'_>, scope: &RouterScope) -> Result<()>
220
242
  &shape.name("flow"),
221
243
  shape.id(1),
222
244
  &shape.flow(),
223
- Some(12),
245
+ Some(&TRANSLATION),
224
246
  flows,
225
247
  )?;
226
248
  }
227
249
  let id = 48 + 8 + u32::from(kind == Kind::Public);
228
- keys::interval(program, &kind.returns(), id, &RETURN, Some(ZONE), returns)?;
250
+ keys::interval(
251
+ program,
252
+ &kind.returns(),
253
+ id,
254
+ &RETURN,
255
+ Some(&ZONE_DATA),
256
+ returns,
257
+ )?;
229
258
  }
230
259
  Ok(())
231
260
  }
@@ -260,15 +289,15 @@ pub(super) fn classification(
260
289
  };
261
290
  if !present(scope, shape, false).is_empty() {
262
291
  let mut expressions = ipv4();
263
- expressions.extend(known(true));
264
- expressions.extend(tuple(true).map(&shape.name("zone")));
292
+ expressions.extend(known(true)?);
293
+ expressions.extend(tuple(true).map(&shape.name("zone"), &shape.classification())?);
265
294
  expressions.push(zone_from_data());
266
295
  program.end("raw_in", expressions, 1)?;
267
296
  }
268
297
  if !present(scope, shape, true).is_empty() {
269
298
  let mut expressions = ipv4();
270
299
  expressions.extend(link(&scope.handoff, false));
271
- expressions.extend(tuple(false).map(&shape.name("zone")));
300
+ expressions.extend(tuple(false).map(&shape.name("zone"), &shape.classification())?);
272
301
  expressions.push(zone_from_data());
273
302
  program.end("raw_out", expressions, 1)?;
274
303
  }
@@ -283,7 +312,7 @@ pub(super) fn classification(
283
312
  .address(false)
284
313
  .port(true)
285
314
  .port(false);
286
- expressions.extend(loads.map(&kind.returns()));
315
+ expressions.extend(loads.map(&kind.returns(), &RETURN)?);
287
316
  expressions.push(zone_from_data());
288
317
  program.end("raw_return", expressions, 1)?;
289
318
  }
@@ -305,8 +334,8 @@ fn flow(shape: Shape, reply: bool) -> Loads {
305
334
  loads.zone();
306
335
  loads
307
336
  }
308
- fn labelled() -> Vec<Attr> {
309
- Loads::new().zone().label().lookup(LABEL_SET)
337
+ fn labelled() -> Result<Vec<Attr>> {
338
+ Loads::new().zone().label().lookup(LABEL_SET, &LABEL_KEY)
310
339
  }
311
340
  /// Admissions of one kind in one filter chain. The original direction leaves
312
341
  /// through the handoff: ESTABLISHED or NEW with the generation's label, or a
@@ -340,36 +369,36 @@ pub(super) fn admissions(
340
369
  };
341
370
  if !original.is_empty() {
342
371
  let mut established = envelope(false, 2);
343
- established.extend(known(true));
344
- established.extend(flow(shape, false).lookup(&name));
345
- established.extend(labelled());
372
+ established.extend(known(true)?);
373
+ established.extend(flow(shape, false).lookup(&name, &shape.flow())?);
374
+ established.extend(labelled()?);
346
375
  program.end(chain, established, 1)?;
347
376
  for transport in &original {
348
- let opening = || {
377
+ let opening = || -> Result<Vec<Attr>> {
349
378
  let mut result = envelope(false, 8);
350
379
  result.extend(meta(16, vec![protocol(transport)]));
351
380
  if *transport == "tcp" {
352
381
  result.extend(opening_tcp());
353
382
  }
354
- result.extend(known(true));
355
- result.extend(flow(shape, false).lookup(&name));
356
- result
383
+ result.extend(known(true)?);
384
+ result.extend(flow(shape, false).lookup(&name, &shape.flow())?);
385
+ Ok(result)
357
386
  };
358
- let mut labelled_opening = opening();
359
- labelled_opening.extend(labelled());
387
+ let mut labelled_opening = opening()?;
388
+ labelled_opening.extend(labelled()?);
360
389
  program.end(chain, labelled_opening, 1)?;
361
- let mut fresh = opening();
390
+ let mut fresh = opening()?;
362
391
  fresh.extend(ct(13, None, vec![0; 16]));
363
- fresh.extend(Loads::new().zone().map(GENERATION_MAP));
392
+ fresh.extend(Loads::new().zone().map(GENERATION_MAP, &GENERATION_KEY)?);
364
393
  fresh.push(keys::set_ct_from_data(13));
365
394
  program.end(chain, fresh, 1)?;
366
395
  }
367
396
  }
368
397
  if reply {
369
398
  let mut answer = envelope(true, 2);
370
- answer.extend(known(false));
371
- answer.extend(flow(shape, true).lookup(&name));
372
- answer.extend(labelled());
399
+ answer.extend(known(false)?);
400
+ answer.extend(flow(shape, true).lookup(&name, &shape.flow())?);
401
+ answer.extend(labelled()?);
373
402
  program.end(chain, answer, 1)?;
374
403
  }
375
404
  }
@@ -389,9 +418,9 @@ pub(super) fn translation(
389
418
  let mut expressions = ipv4();
390
419
  expressions.extend(link(&scope.handoff, false));
391
420
  expressions.extend(ct(1, None, vec![0]));
392
- expressions.extend(labelled());
393
- expressions.extend(known(true));
394
- expressions.extend(flow(shape, false).map(&shape.name("flow")));
421
+ expressions.extend(labelled()?);
422
+ expressions.extend(known(true)?);
423
+ expressions.extend(flow(shape, false).map(&shape.name("flow"), &shape.flow())?);
395
424
  expressions.push(super::published::nat(0, 2));
396
425
  program.rule("post", expressions)?;
397
426
  }
@@ -1,5 +1,14 @@
1
+ use super::keys::{
2
+ self, Field, Key, Loads, CT_ORIGINAL_DPORT, CT_REPLY_SADDR, CT_REPLY_SPORT, L4PROTO,
3
+ };
1
4
  use super::*;
2
5
 
6
+ /// Publications translated into a pool: the protocol, the address and port the
7
+ /// flow was translated to, and its originally tracked destination port. Port fields are inclusive intervals, so one element holds
8
+ /// one port or a whole range.
9
+ const PUBLISHED: &str = "pool_published";
10
+ const PUBLISHED_KEY: [Field; 4] = [L4PROTO, CT_REPLY_SADDR, CT_REPLY_SPORT, CT_ORIGINAL_DPORT];
11
+
3
12
  pub(super) fn compile(program: &mut Program<'_>, scope: &AllocationPoolGuardScope) -> Result<()> {
4
13
  for (name, hook) in [("input", 1), ("forward", 2), ("output", 3)] {
5
14
  program.chain(name, Some((hook, 0, "filter")))?;
@@ -18,6 +27,9 @@ pub(super) fn compile(program: &mut Program<'_>, scope: &AllocationPoolGuardScop
18
27
  hostgrant::tuple_set(program, &scope.host_grants, |_| Ok(None))?;
19
28
  hostgrant::admissions(program, ["output", "input"], false, |_| vec![])?;
20
29
  }
30
+ if !scope.published_ports.is_empty() {
31
+ published_set(program, scope)?;
32
+ }
21
33
  for name in ["input", "forward", "output"] {
22
34
  program.jump(name, ipv4(), "pool_guard")?;
23
35
  }
@@ -25,6 +37,11 @@ pub(super) fn compile(program: &mut Program<'_>, scope: &AllocationPoolGuardScop
25
37
  for prefix in &scope.prefixes {
26
38
  program.end("pool_guard", original_address(prefix, false)?, 0)?;
27
39
  }
40
+ // Publications follow that denial, so only a flow translated from outside
41
+ // every pool can match one, and precede every current-address denial.
42
+ if !scope.published_ports.is_empty() {
43
+ published(program, scope)?;
44
+ }
28
45
  // GOTO, not JUMP: RETURN from pool_reply must return to the base chain,
29
46
  // skipping current-destination denial for legitimate reverse-SNAT replies.
30
47
  let mut reply = ct(1, None, vec![1]);
@@ -40,6 +57,61 @@ pub(super) fn compile(program: &mut Program<'_>, scope: &AllocationPoolGuardScop
40
57
  program.end("pool_reply", vec![], -5)
41
58
  }
42
59
 
60
+ /// The publications of the scope as one interval set.
61
+ fn published_set(program: &mut Program<'_>, scope: &AllocationPoolGuardScope) -> Result<()> {
62
+ let mut elements = Vec::new();
63
+ for port in &scope.published_ports {
64
+ elements.push(
65
+ Key::default()
66
+ .protocol(&port.protocol)
67
+ .address(&port.target_address)?
68
+ .span(port.target_port, port.target_last())
69
+ .span(port.host_port, port.host_last())
70
+ .element(None),
71
+ );
72
+ }
73
+ keys::interval(program, PUBLISHED, 2, &PUBLISHED_KEY, None, elements).map(|_| ())
74
+ }
75
+ /// The admissions of every publication, whatever their number: behind the
76
+ /// default conntrack zone, an ESTABLISHED packet of either direction, and a NEW
77
+ /// opening per protocol (TCP only with SYN and FIN/RST/ACK clear). Each looks its
78
+ /// flow up once by the reply tuple's source, which is the address and port the
79
+ /// flow was translated to in both directions, and the originally tracked
80
+ /// destination port. They run in the guard chain after the original-destination
81
+ /// denial, so a flow whose original destination lies in a pool never reaches
82
+ /// them. A publication is the exception of every hook it crosses: on a host that
83
+ /// forwards it, that is FORWARD alone.
84
+ fn published(program: &mut Program<'_>, scope: &AllocationPoolGuardScope) -> Result<()> {
85
+ let lookup = || {
86
+ let mut loads = Loads::new();
87
+ loads.protocol().translated().original_port(false);
88
+ loads.lookup(PUBLISHED, &PUBLISHED_KEY)
89
+ };
90
+ let envelope = |connection_state: u32| {
91
+ let mut result = ct(17, None, 0_u16.to_ne_bytes().to_vec());
92
+ result.extend(state(connection_state));
93
+ result
94
+ };
95
+ let mut established = envelope(2);
96
+ established.extend(lookup()?);
97
+ program.end("pool_guard", established, 1)?;
98
+ let transports: std::collections::BTreeSet<&str> = scope
99
+ .published_ports
100
+ .iter()
101
+ .map(|port| port.protocol.as_str())
102
+ .collect();
103
+ for transport in transports {
104
+ let mut opening = envelope(8);
105
+ opening.extend(meta(16, vec![protocol(transport)]));
106
+ if transport == "tcp" {
107
+ opening.extend(opening_tcp());
108
+ }
109
+ opening.extend(lookup()?);
110
+ program.end("pool_guard", opening, 1)?;
111
+ }
112
+ Ok(())
113
+ }
114
+
43
115
  /// The exact loopback TCP destination: IPv4, TCP, address and port.
44
116
  fn local_tcp_port(owner: &LocalTcpPortOwner) -> Result<Vec<Attr>> {
45
117
  let mut result = ipv4();
@@ -4,7 +4,9 @@
4
4
  //! anti-spoof guard, the directed rules and the terminal denials look the packet
5
5
  //! up there and in sets keyed on the link index, so a renamed or reused interface
6
6
  //! never matches an element of another link.
7
- use super::keys::{self, Key, Loads, ADDRESS, INDEX, NAME, PORT, PROTOCOL};
7
+ use super::keys::{
8
+ self, Field, Key, Loads, IIF, IIFNAME, IP_DADDR, IP_SADDR, L4PROTO, OIF, TH_DPORT, TH_SPORT,
9
+ };
8
10
  use super::*;
9
11
 
10
12
  /// Every endpoint link, index and name, and the absent interface (0, "") of
@@ -21,6 +23,14 @@ const SOURCE: &str = "private_source";
21
23
  const ANY: &str = "private_any";
22
24
  const PORTED: &str = "private_port";
23
25
 
26
+ /// The keys, as the incoming direction loads them where both directions do.
27
+ const LINK_KEY: [Field; 2] = [IIF, IIFNAME];
28
+ const INDEX_KEY: [Field; 1] = [IIF];
29
+ const NAME_KEY: [Field; 1] = [IIFNAME];
30
+ const SOURCE_KEY: [Field; 2] = [IIF, IP_SADDR];
31
+ const ANY_KEY: [Field; 4] = [IIF, OIF, IP_SADDR, IP_DADDR];
32
+ const PORTED_KEY: [Field; 7] = [IIF, OIF, IP_SADDR, IP_DADDR, L4PROTO, TH_SPORT, TH_DPORT];
33
+
24
34
  fn name(value: &str) -> Vec<u8> {
25
35
  let mut bytes = value.as_bytes().to_vec();
26
36
  bytes.resize(16, 0);
@@ -64,10 +74,10 @@ pub(super) fn sets(program: &mut Program<'_>, scope: &RouterScope) -> Result<()>
64
74
  );
65
75
  }
66
76
  }
67
- keys::exact(program, LINK, 32, INDEX + NAME, None, links)?;
68
- keys::exact(program, INDEX_SET, 33, INDEX, None, indices)?;
69
- keys::exact(program, NAME_SET, 34, NAME, None, names)?;
70
- keys::interval(program, SOURCE, 35, &[INDEX, ADDRESS], None, sources)?;
77
+ keys::exact(program, LINK, 32, &LINK_KEY, None, links)?;
78
+ keys::exact(program, INDEX_SET, 33, &INDEX_KEY, None, indices)?;
79
+ keys::exact(program, NAME_SET, 34, &NAME_KEY, None, names)?;
80
+ keys::interval(program, SOURCE, 35, &SOURCE_KEY, None, sources)?;
71
81
  let (mut any, mut ported) = (Vec::new(), Vec::new());
72
82
  for rule in &scope.rules {
73
83
  let key = Key::default()
@@ -86,22 +96,8 @@ pub(super) fn sets(program: &mut Program<'_>, scope: &RouterScope) -> Result<()>
86
96
  );
87
97
  }
88
98
  }
89
- keys::interval(
90
- program,
91
- ANY,
92
- 36,
93
- &[INDEX, INDEX, ADDRESS, ADDRESS],
94
- None,
95
- any,
96
- )?;
97
- keys::interval(
98
- program,
99
- PORTED,
100
- 37,
101
- &[INDEX, INDEX, ADDRESS, ADDRESS, PROTOCOL, PORT, PORT],
102
- None,
103
- ported,
104
- )?;
99
+ keys::interval(program, ANY, 36, &ANY_KEY, None, any)?;
100
+ keys::interval(program, PORTED, 37, &PORTED_KEY, None, ported)?;
105
101
  Ok(())
106
102
  }
107
103
  /// The directions a filter chain sees an endpoint link in.
@@ -114,8 +110,8 @@ fn directions(chain: &str) -> Vec<bool> {
114
110
  }
115
111
  /// The interface of one direction is an endpoint link or absent, by index and
116
112
  /// name.
117
- pub(super) fn known(incoming: bool) -> Vec<Attr> {
118
- Loads::new().link(incoming).lookup(LINK)
113
+ pub(super) fn known(incoming: bool) -> Result<Vec<Attr>> {
114
+ Loads::new().link(incoming).lookup(LINK, &LINK_KEY)
119
115
  }
120
116
  /// Anti-spoof guards: a packet arriving from an endpoint link must carry one of
121
117
  /// its source prefixes, and one leaving to it one of its prefixes as destination;
@@ -126,7 +122,7 @@ pub(super) fn guards(program: &mut Program<'_>, scope: &RouterScope, chain: &str
126
122
  return Ok(());
127
123
  }
128
124
  for incoming in directions(chain) {
129
- let mut foreign = known(incoming);
125
+ let mut foreign = known(incoming)?;
130
126
  foreign.push(expr("meta", vec![Attr::u32(1, 1), Attr::u32(2, 15)]));
131
127
  foreign.push(expr(
132
128
  "cmp",
@@ -138,12 +134,12 @@ pub(super) fn guards(program: &mut Program<'_>, scope: &RouterScope, chain: &str
138
134
  ));
139
135
  program.end(chain, foreign, 0)?;
140
136
  let mut spoofed = ipv4();
141
- spoofed.extend(known(incoming));
137
+ spoofed.extend(known(incoming)?);
142
138
  spoofed.extend(
143
139
  Loads::new()
144
140
  .index(incoming)
145
141
  .address(incoming)
146
- .absent(SOURCE),
142
+ .absent(SOURCE, &SOURCE_KEY)?,
147
143
  );
148
144
  program.end(chain, spoofed, 0)?;
149
145
  }
@@ -154,7 +150,7 @@ pub(super) fn guards(program: &mut Program<'_>, scope: &RouterScope, chain: &str
154
150
  /// protocol and ports. Input sees no outgoing and output no incoming interface,
155
151
  /// so each chain matches exactly the rules that were compiled into it.
156
152
  pub(super) fn grants(program: &mut Program<'_>, scope: &RouterScope, chain: &str) -> Result<()> {
157
- for (set, ported) in [(ANY, false), (PORTED, true)] {
153
+ for (set, key, ported) in [(ANY, &ANY_KEY[..], false), (PORTED, &PORTED_KEY[..], true)] {
158
154
  if !scope
159
155
  .rules
160
156
  .iter()
@@ -163,14 +159,14 @@ pub(super) fn grants(program: &mut Program<'_>, scope: &RouterScope, chain: &str
163
159
  continue;
164
160
  }
165
161
  let mut expressions = ipv4();
166
- expressions.extend(known(true));
167
- expressions.extend(known(false));
162
+ expressions.extend(known(true)?);
163
+ expressions.extend(known(false)?);
168
164
  let mut loads = Loads::new();
169
165
  loads.index(true).index(false).address(true).address(false);
170
166
  if ported {
171
167
  loads.protocol().port(true).port(false);
172
168
  }
173
- expressions.extend(loads.lookup(set));
169
+ expressions.extend(loads.lookup(set, key)?);
174
170
  program.end(chain, expressions, 1)?;
175
171
  }
176
172
  Ok(())
@@ -181,15 +177,14 @@ pub(super) fn denials(program: &mut Program<'_>, scope: &RouterScope, chain: &st
181
177
  return Ok(());
182
178
  }
183
179
  for incoming in directions(chain) {
184
- program.end(chain, Loads::new().index(incoming).lookup(INDEX_SET), 0)?;
185
180
  program.end(
186
181
  chain,
187
- Loads::new()
188
- .push(
189
- |at| super::hostgrant::meta_to(if incoming { 6 } else { 7 }, at),
190
- NAME,
191
- )
192
- .lookup(NAME_SET),
182
+ Loads::new().index(incoming).lookup(INDEX_SET, &INDEX_KEY)?,
183
+ 0,
184
+ )?;
185
+ program.end(
186
+ chain,
187
+ Loads::new().name(incoming).lookup(NAME_SET, &NAME_KEY)?,
193
188
  0,
194
189
  )?;
195
190
  }