@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,8 +6,12 @@
6
6
  //! protocols, the live address and port, and the originally tracked address and
7
7
  //! port. A port field is an inclusive interval, so one element holds one port or
8
8
  //! a whole range, and every other field is exact.
9
- use super::hostgrant::{meta_to, register};
10
- use super::keys::{self, word, Key, Loads, ADDRESS, INDEX, NAME, PORT, PROTOCOL};
9
+ use super::hostgrant::register;
10
+ use super::keys::{
11
+ self, word, Field, Key, Loads, CT_ORIGINAL_DADDR, CT_ORIGINAL_DPORT, CT_ORIGINAL_SADDR,
12
+ CT_ORIGINAL_SPORT, CT_PROTOCOL, IIF, IIFNAME, IP_DADDR, IP_SADDR, L4PROTO, OIF, OIFNAME,
13
+ TH_DPORT, TH_SPORT,
14
+ };
11
15
  use super::*;
12
16
 
13
17
  /// Router handoff arrivals of a publication, classified before conntrack.
@@ -24,7 +28,39 @@ pub(super) const FORWARD: &str = "published_forward";
24
28
  /// the data is the source NAT to the outside address and port span.
25
29
  pub(super) const SYMMETRIC: &str = "published_symmetric";
26
30
 
27
- const LINK: [u32; 2] = [INDEX, NAME];
31
+ const ARRIVAL_KEY: [Field; 3] = [L4PROTO, IP_DADDR, TH_DPORT];
32
+ const ENDPOINT_KEY: [Field; 5] = [IIF, IIFNAME, L4PROTO, IP_SADDR, TH_SPORT];
33
+ /// The translation maps' key, and their data: the inside address and port, or
34
+ /// the inside address alone.
35
+ const TRANSLATION_KEY: [Field; 4] = [L4PROTO, CT_PROTOCOL, IP_DADDR, TH_DPORT];
36
+ const PORT_DATA: [Field; 2] = [IP_DADDR, TH_DPORT];
37
+ const ADDRESS_DATA: [Field; 1] = [IP_DADDR];
38
+ /// `[link, protocols, address, port, original address, original port]`, the key
39
+ /// of both admission sets, as their original direction loads it: inbound
40
+ /// toward the inside link and destination, symmetric from them.
41
+ const INBOUND_KEY: [Field; 8] = [
42
+ OIF,
43
+ OIFNAME,
44
+ L4PROTO,
45
+ CT_PROTOCOL,
46
+ IP_DADDR,
47
+ TH_DPORT,
48
+ CT_ORIGINAL_DADDR,
49
+ CT_ORIGINAL_DPORT,
50
+ ];
51
+ const SYMMETRIC_KEY: [Field; 8] = [
52
+ IIF,
53
+ IIFNAME,
54
+ L4PROTO,
55
+ CT_PROTOCOL,
56
+ IP_SADDR,
57
+ TH_SPORT,
58
+ CT_ORIGINAL_SADDR,
59
+ CT_ORIGINAL_SPORT,
60
+ ];
61
+ /// The source NAT a symmetric flow stores: the outside address and the first
62
+ /// and last port of its span.
63
+ const SYMMETRIC_DATA: [Field; 3] = [IP_SADDR, TH_SPORT, TH_SPORT];
28
64
 
29
65
  /// One publication on one hop. The outside is what clients reach (the uplink
30
66
  /// address and port on the host, the transit address and port in the router);
@@ -44,13 +80,6 @@ pub(super) struct Hop<'a> {
44
80
  pub(super) fixed: &'a LocalLink,
45
81
  }
46
82
 
47
- /// `[link, protocols, address, port, original address, original port]`, the key
48
- /// of both admission sets.
49
- fn flow_fields() -> Vec<u32> {
50
- let mut fields = LINK.to_vec();
51
- fields.extend([PROTOCOL, PROTOCOL, ADDRESS, PORT, ADDRESS, PORT]);
52
- fields
53
- }
54
83
  fn inbound_key(publication: &Publication<'_>) -> Result<IntervalElement> {
55
84
  let (outside, outside_first, outside_last) = publication.outside;
56
85
  let (inside, inside_first, inside_last) = publication.inside;
@@ -108,9 +137,6 @@ fn translation_loads() -> Loads {
108
137
  loads.protocols().address(false).port(false);
109
138
  loads
110
139
  }
111
- fn translation_fields() -> [u32; 4] {
112
- [PROTOCOL, PROTOCOL, ADDRESS, PORT]
113
- }
114
140
  fn ranged(publication: &Publication<'_>) -> bool {
115
141
  publication.outside.1 != publication.outside.2
116
142
  }
@@ -133,14 +159,7 @@ pub(super) fn sets(program: &mut Program<'_>, hop: &Hop<'_>, router: bool) -> Re
133
159
  .element(None),
134
160
  );
135
161
  }
136
- declare(
137
- program,
138
- ARRIVAL,
139
- 16,
140
- &[PROTOCOL, ADDRESS, PORT],
141
- None,
142
- arrivals,
143
- )?;
162
+ declare(program, ARRIVAL, 16, &ARRIVAL_KEY, None, arrivals)?;
144
163
  // Several publications may share inside ports that are not symmetric;
145
164
  // the classification is their union, as disjoint intervals.
146
165
  let mut endpoints = Vec::new();
@@ -155,9 +174,7 @@ pub(super) fn sets(program: &mut Program<'_>, hop: &Hop<'_>, router: bool) -> Re
155
174
  .element(None),
156
175
  );
157
176
  }
158
- let mut fields = LINK.to_vec();
159
- fields.extend([PROTOCOL, ADDRESS, PORT]);
160
- declare(program, ENDPOINT, 17, &fields, None, endpoints)?;
177
+ declare(program, ENDPOINT, 17, &ENDPOINT_KEY, None, endpoints)?;
161
178
  }
162
179
  let (mut ports, mut addresses) = (Vec::new(), Vec::new());
163
180
  for publication in &hop.publications {
@@ -175,13 +192,20 @@ pub(super) fn sets(program: &mut Program<'_>, hop: &Hop<'_>, router: bool) -> Re
175
192
  ports.push(key.element(Some(data)));
176
193
  }
177
194
  }
178
- declare(program, PORT_MAP, 18, &translation_fields(), Some(8), ports)?;
195
+ declare(
196
+ program,
197
+ PORT_MAP,
198
+ 18,
199
+ &TRANSLATION_KEY,
200
+ Some(&PORT_DATA),
201
+ ports,
202
+ )?;
179
203
  declare(
180
204
  program,
181
205
  ADDRESS_MAP,
182
206
  19,
183
- &translation_fields(),
184
- Some(4),
207
+ &TRANSLATION_KEY,
208
+ Some(&ADDRESS_DATA),
185
209
  addresses,
186
210
  )?;
187
211
  let inbound = hop
@@ -189,21 +213,28 @@ pub(super) fn sets(program: &mut Program<'_>, hop: &Hop<'_>, router: bool) -> Re
189
213
  .iter()
190
214
  .map(inbound_key)
191
215
  .collect::<Result<Vec<_>>>()?;
192
- declare(program, FORWARD, 20, &flow_fields(), None, inbound)?;
216
+ declare(program, FORWARD, 20, &INBOUND_KEY, None, inbound)?;
193
217
  let symmetric = hop
194
218
  .publications
195
219
  .iter()
196
220
  .filter(|publication| publication.symmetric)
197
221
  .map(symmetric_key)
198
222
  .collect::<Result<Vec<_>>>()?;
199
- declare(program, SYMMETRIC, 21, &flow_fields(), Some(12), symmetric)
223
+ declare(
224
+ program,
225
+ SYMMETRIC,
226
+ 21,
227
+ &SYMMETRIC_KEY,
228
+ Some(&SYMMETRIC_DATA),
229
+ symmetric,
230
+ )
200
231
  }
201
232
  fn declare(
202
233
  program: &mut Program<'_>,
203
234
  name: &str,
204
235
  id: u32,
205
- fields: &[u32],
206
- data: Option<u32>,
236
+ fields: &[Field],
237
+ data: Option<&[Field]>,
207
238
  elements: Vec<IntervalElement>,
208
239
  ) -> Result<()> {
209
240
  keys::interval(program, name, id, fields, data, elements).map(|_| ())
@@ -217,6 +248,7 @@ fn admissions(
217
248
  program: &mut Program<'_>,
218
249
  hop: &Hop<'_>,
219
250
  set: &str,
251
+ key: &[Field],
220
252
  protocols: &BTreeSet<&str>,
221
253
  fixed_incoming: bool,
222
254
  loads: fn(bool) -> Loads,
@@ -233,7 +265,7 @@ fn admissions(
233
265
  result
234
266
  };
235
267
  let mut established = envelope(false, 2);
236
- established.extend(loads(false).lookup(set));
268
+ established.extend(loads(false).lookup(set, key)?);
237
269
  program.end("forward", established, 1)?;
238
270
  for transport in protocols {
239
271
  let mut opening = envelope(false, 8);
@@ -241,11 +273,11 @@ fn admissions(
241
273
  if *transport == "tcp" {
242
274
  opening.extend(opening_tcp());
243
275
  }
244
- opening.extend(loads(false).lookup(set));
276
+ opening.extend(loads(false).lookup(set, key)?);
245
277
  program.end("forward", opening, 1)?;
246
278
  }
247
279
  let mut reply = envelope(true, 2);
248
- reply.extend(loads(true).lookup(set));
280
+ reply.extend(loads(true).lookup(set, key)?);
249
281
  program.end("forward", reply, 1)
250
282
  }
251
283
  fn protocols<'a>(hop: &Hop<'a>, symmetric: bool) -> BTreeSet<&'a str> {
@@ -262,6 +294,7 @@ pub(super) fn inbound(program: &mut Program<'_>, hop: &Hop<'_>) -> Result<()> {
262
294
  program,
263
295
  hop,
264
296
  FORWARD,
297
+ &INBOUND_KEY,
265
298
  &protocols(hop, false),
266
299
  true,
267
300
  inbound_loads,
@@ -275,6 +308,7 @@ pub(super) fn symmetric(program: &mut Program<'_>, hop: &Hop<'_>) -> Result<()>
275
308
  program,
276
309
  hop,
277
310
  SYMMETRIC,
311
+ &SYMMETRIC_KEY,
278
312
  &protocols(hop, true),
279
313
  false,
280
314
  symmetric_loads,
@@ -317,7 +351,7 @@ pub(super) fn translations(program: &mut Program<'_>, hop: &Hop<'_>) -> Result<(
317
351
  ] {
318
352
  if present {
319
353
  let mut expressions = tracked(hop.fixed, true);
320
- expressions.extend(translation_loads().map(set));
354
+ expressions.extend(translation_loads().map(set, &TRANSLATION_KEY)?);
321
355
  expressions.push(nat(1, ports));
322
356
  program.rule("pre", expressions)?;
323
357
  }
@@ -329,7 +363,7 @@ pub(super) fn translations(program: &mut Program<'_>, hop: &Hop<'_>) -> Result<(
329
363
  pub(super) fn symmetric_translation(program: &mut Program<'_>, hop: &Hop<'_>) -> Result<()> {
330
364
  if hop.publications.iter().any(|item| item.symmetric) {
331
365
  let mut expressions = tracked(hop.fixed, false);
332
- expressions.extend(symmetric_loads(false).map(SYMMETRIC));
366
+ expressions.extend(symmetric_loads(false).map(SYMMETRIC, &SYMMETRIC_KEY)?);
333
367
  expressions.push(nat(0, 2));
334
368
  program.rule("post", expressions)?;
335
369
  }
@@ -343,11 +377,8 @@ pub(super) fn arrival(program: &mut Program<'_>, hop: &Hop<'_>) -> Result<()> {
343
377
  let mut expressions = ipv4();
344
378
  expressions.extend(link(hop.fixed, true));
345
379
  let mut loads = Loads::new();
346
- loads
347
- .push(|index| meta_to(16, index), PROTOCOL)
348
- .address(false)
349
- .port(false);
350
- expressions.extend(loads.lookup(ARRIVAL));
380
+ loads.protocol().address(false).port(false);
381
+ expressions.extend(loads.lookup(ARRIVAL, &ARRIVAL_KEY)?);
351
382
  program.end("raw_return", expressions, 1)?;
352
383
  }
353
384
  Ok(())
@@ -358,12 +389,8 @@ pub(super) fn endpoint(program: &mut Program<'_>, hop: &Hop<'_>) -> Result<()> {
358
389
  if !hop.publications.is_empty() {
359
390
  let mut expressions = ipv4();
360
391
  let mut loads = Loads::new();
361
- loads
362
- .link(true)
363
- .push(|index| meta_to(16, index), PROTOCOL)
364
- .address(true)
365
- .port(true);
366
- expressions.extend(loads.lookup(ENDPOINT));
392
+ loads.link(true).protocol().address(true).port(true);
393
+ expressions.extend(loads.lookup(ENDPOINT, &ENDPOINT_KEY)?);
367
394
  program.end("raw_in", expressions, 1)?;
368
395
  }
369
396
  Ok(())
@@ -57,7 +57,7 @@ fn host_grant_return(program: &mut Program<'_>, scope: &RouterScope) -> Result<(
57
57
  if !scope.host_grants.is_empty() {
58
58
  let mut expressions = ipv4();
59
59
  expressions.extend(link(&scope.handoff, true));
60
- expressions.extend(hostgrant::arrival_lookup());
60
+ expressions.extend(hostgrant::arrival_lookup()?);
61
61
  program.end("raw_return", expressions, 1)?;
62
62
  }
63
63
  Ok(())
@@ -282,15 +282,43 @@ pub struct LocalTcpPortOwner {
282
282
  pub port: u16,
283
283
  pub uid: u32,
284
284
  }
285
+ /// One publication a guard admits into a pool: a forwarded flow whose original
286
+ /// destination lies outside every guarded pool, on the published port or range,
287
+ /// translated to exactly `target_address` and the target port(s), and its replies.
288
+ /// A range keeps every port: `target_port` equals `host_port`. Absent for one port.
289
+ #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord)]
290
+ #[serde(rename_all = "camelCase", deny_unknown_fields)]
291
+ pub struct PoolGuardPublishedPort {
292
+ pub protocol: String,
293
+ pub host_port: u16,
294
+ #[serde(default, skip_serializing_if = "Option::is_none")]
295
+ pub host_port_end: Option<u16>,
296
+ pub target_port: u16,
297
+ pub target_address: String,
298
+ }
299
+ impl PoolGuardPublishedPort {
300
+ /// Last published port; equal to `host_port` for one port.
301
+ pub fn host_last(&self) -> u16 {
302
+ self.host_port_end.unwrap_or(self.host_port)
303
+ }
304
+ /// Last target port the publication translates to.
305
+ pub fn target_last(&self) -> u16 {
306
+ self.target_port + (self.host_last() - self.host_port)
307
+ }
308
+ }
285
309
  #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
286
310
  #[serde(rename_all = "camelCase", deny_unknown_fields)]
287
311
  pub struct AllocationPoolGuardScope {
288
312
  pub authority_digest: String,
289
313
  pub prefixes: Vec<String>,
290
- /// The only exceptions to the guard: exact host-origin flows into a pool.
314
+ /// Exceptions to the guard: exact host-origin flows into a pool.
291
315
  /// Absent and empty are the same canonical policy.
292
316
  #[serde(default, skip_serializing_if = "Vec::is_empty")]
293
317
  pub host_grants: Vec<HostGrant>,
318
+ /// Exceptions to the guard: forwarded publications translated into a pool,
319
+ /// and their replies. Absent and empty are the same canonical policy.
320
+ #[serde(default, skip_serializing_if = "Vec::is_empty")]
321
+ pub published_ports: Vec<PoolGuardPublishedPort>,
294
322
  /// Host-wide loopback port ownership. Absent and empty are the same
295
323
  /// canonical policy.
296
324
  #[serde(default, skip_serializing_if = "Vec::is_empty")]
@@ -974,8 +1002,33 @@ fn normalize_pool_guard(value: &mut AllocationPoolGuardScope) -> Result<()> {
974
1002
  require(covers_address(&value.prefixes, &grant.destination_address)?)
975
1003
  })?;
976
1004
  value.host_grants = grants;
1005
+ normalize_pool_guard_published(value)?;
977
1006
  normalize_local_tcp_port_owners(&mut value.local_tcp_port_owners)
978
1007
  }
1008
+ /// A guard publication translates into exactly one address of a guarded pool,
1009
+ /// keeping a range's ports, and publishes each protocol and port at most once,
1010
+ /// as a host-transit scope does: sorted by protocol and first port, disjoint
1011
+ /// neighbours are disjoint spans.
1012
+ fn normalize_pool_guard_published(value: &mut AllocationPoolGuardScope) -> Result<()> {
1013
+ let mut ports = std::mem::take(&mut value.published_ports);
1014
+ crate::capacity("publishedPorts", PUBLISHED_PORTS, ports.len())?;
1015
+ ports.sort();
1016
+ for (index, port) in ports.iter().enumerate() {
1017
+ require(
1018
+ protocol(&port.protocol)
1019
+ && port.host_port > 0
1020
+ && port.target_port > 0
1021
+ && published_span(port.host_port, port.host_port_end, port.target_port)
1022
+ && (index == 0
1023
+ || ports[index - 1].protocol != port.protocol
1024
+ || ports[index - 1].host_last() < port.host_port),
1025
+ )?;
1026
+ unicast(&port.target_address)?;
1027
+ require(covers_address(&value.prefixes, &port.target_address)?)?;
1028
+ }
1029
+ value.published_ports = ports;
1030
+ Ok(())
1031
+ }
979
1032
  /// One exact IPv4 loopback host address and port per entry, never a prefix,
980
1033
  /// wildcard or range, each with exactly one owning uid. `(uid_t)-1` is not a
981
1034
  /// user; it is the kernel's "unchanged" sentinel.
@@ -1242,6 +1295,9 @@ pub(crate) mod localplatform_tests;
1242
1295
  #[path = "egress_localport_tests.rs"]
1243
1296
  pub(crate) mod localport_tests;
1244
1297
  #[cfg(test)]
1298
+ #[path = "egress_poolguardpublished_tests.rs"]
1299
+ mod poolguardpublished_tests;
1300
+ #[cfg(test)]
1245
1301
  #[path = "egress_publishedrange_tests.rs"]
1246
1302
  mod publishedrange_tests;
1247
1303
  #[cfg(test)]
@@ -1288,6 +1344,14 @@ impl Prepared {
1288
1344
  pub fn program(&self, table: &str) -> Result<Vec<(u16, Vec<crate::wire::Attr>)>> {
1289
1345
  compile::compile(self, table)
1290
1346
  }
1347
+ /// Every set the compiled graph declares, with its key and data fields.
1348
+ #[cfg(test)]
1349
+ pub(crate) fn declared(
1350
+ &self,
1351
+ table: &str,
1352
+ ) -> Result<std::collections::BTreeMap<String, compile::SetDeclaration>> {
1353
+ compile::declared(self, table)
1354
+ }
1291
1355
  pub fn validate_interfaces(&self) -> Result<()> {
1292
1356
  let links: Vec<&LocalLink> = match &self.policy.scope {
1293
1357
  Scope::RouterEgress(scope) => scope
@@ -6,7 +6,8 @@
6
6
  //! exact link that holds it (index and name), and both the incoming and the
7
7
  //! outgoing link are looked up on every packet. Only the grant's source opens;
8
8
  //! the destination only answers.
9
- use super::hostgrant::{lookup, meta_to, payload_to, tuple_key, tuple_lookup};
9
+ use super::hostgrant::{tuple_key, tuple_lookup, TUPLE};
10
+ use super::keys::{Field, Loads, IIF, IIFNAME, IP_SADDR};
10
11
  use super::*;
11
12
  use std::collections::BTreeSet;
12
13
 
@@ -14,16 +15,16 @@ use std::collections::BTreeSet;
14
15
  pub(super) const TUPLE_SET: &str = "workload_grant";
15
16
  /// Every grant address with the exact link that holds it.
16
17
  pub(super) const LINK_SET: &str = "workload_link";
18
+ /// A grant address and its link, as the incoming direction loads them.
19
+ const LINK: [Field; 3] = [IIF, IIFNAME, IP_SADDR];
17
20
 
18
21
  /// The link a packet crosses and the address on its side of that link: the
19
22
  /// incoming link and the source, or the outgoing link and the destination.
20
- fn link_lookup(incoming: bool) -> Vec<Attr> {
21
- vec![
22
- meta_to(if incoming { 4 } else { 5 }, 4),
23
- meta_to(if incoming { 6 } else { 7 }, 5),
24
- payload_to(1, if incoming { 12 } else { 16 }, 4, 9),
25
- lookup(LINK_SET),
26
- ]
23
+ fn link_lookup(incoming: bool) -> Result<Vec<Attr>> {
24
+ Loads::new()
25
+ .link(incoming)
26
+ .address(incoming)
27
+ .lookup(LINK_SET, &LINK)
27
28
  }
28
29
  fn link_key(link: &LocalLink, address: &str) -> Result<Vec<u8>> {
29
30
  let mut key = link.interface_index.to_ne_bytes().to_vec();
@@ -49,9 +50,9 @@ pub(super) fn sets(program: &mut Program<'_>, scope: &RouterScope) -> Result<()>
49
50
  )?);
50
51
  }
51
52
  }
52
- program.set(TUPLE_SET, 3, 32)?;
53
+ program.exact_set(TUPLE_SET, 3, &TUPLE, None)?;
53
54
  program.elements(TUPLE_SET, keys)?;
54
- program.set(LINK_SET, 4, 24)?;
55
+ program.exact_set(LINK_SET, 4, &LINK, None)?;
55
56
  program.elements(LINK_SET, links.into_iter().collect())
56
57
  }
57
58
 
@@ -64,25 +65,26 @@ pub(super) fn admissions(program: &mut Program<'_>, scope: &RouterScope) -> Resu
64
65
  if scope.workload_grants.is_empty() {
65
66
  return Ok(());
66
67
  }
67
- let admission = |reply: bool, connection_state: u32, transport: Option<&str>| {
68
- let mut result = ipv4();
69
- result.extend(ct(17, None, 0_u16.to_ne_bytes().to_vec()));
70
- result.extend(ct(1, None, vec![u8::from(reply)]));
71
- result.extend(state(connection_state));
72
- if let Some(transport) = transport {
73
- result.extend(meta(16, vec![protocol(transport)]));
74
- if transport == "tcp" {
75
- result.extend(opening_tcp());
68
+ let admission =
69
+ |reply: bool, connection_state: u32, transport: Option<&str>| -> Result<Vec<Attr>> {
70
+ let mut result = ipv4();
71
+ result.extend(ct(17, None, 0_u16.to_ne_bytes().to_vec()));
72
+ result.extend(ct(1, None, vec![u8::from(reply)]));
73
+ result.extend(state(connection_state));
74
+ if let Some(transport) = transport {
75
+ result.extend(meta(16, vec![protocol(transport)]));
76
+ if transport == "tcp" {
77
+ result.extend(opening_tcp());
78
+ }
76
79
  }
77
- }
78
- result.extend(link_lookup(true));
79
- result.extend(link_lookup(false));
80
- result.extend(tuple_lookup(TUPLE_SET, false, reply));
81
- result
82
- };
83
- program.end("forward", admission(false, 2, None), 1)?;
80
+ result.extend(link_lookup(true)?);
81
+ result.extend(link_lookup(false)?);
82
+ result.extend(tuple_lookup(TUPLE_SET, false, reply)?);
83
+ Ok(result)
84
+ };
85
+ program.end("forward", admission(false, 2, None)?, 1)?;
84
86
  for transport in ["udp", "tcp"] {
85
- program.end("forward", admission(false, 8, Some(transport)), 1)?;
87
+ program.end("forward", admission(false, 8, Some(transport))?, 1)?;
86
88
  }
87
- program.end("forward", admission(true, 2, None), 1)
89
+ program.end("forward", admission(true, 2, None)?, 1)
88
90
  }
@@ -357,6 +357,18 @@ fn load(flow: &Flow, name: &str, fields: &[Attr]) -> Option<Vec<u8>> {
357
357
  assert_eq!(loaded.len() as u32, wire::number(fields, 4).unwrap());
358
358
  loaded
359
359
  }
360
+ "ct" if wire::one(fields, 3).is_ok_and(|direction| direction.value == vec![1]) => {
361
+ // The reply tuple's source is where the flow was translated to: the
362
+ // live destination of the original direction, the live source of the
363
+ // reply, as a filter hook after destination NAT sees them.
364
+ match wire::number(fields, 2).unwrap() {
365
+ 19 if flow.reply => flow.source.to_vec(),
366
+ 19 => flow.destination.to_vec(),
367
+ 11 if flow.reply => flow.source_port.to_be_bytes().to_vec(),
368
+ 11 => flow.destination_port.to_be_bytes().to_vec(),
369
+ key => panic!("ct reply {key}"),
370
+ }
371
+ }
360
372
  "ct" => {
361
373
  if let Ok(direction) = wire::one(fields, 3) {
362
374
  assert_eq!(direction.value, vec![0]);
@@ -0,0 +1,150 @@
1
+ //! Set declarations against what nftables 1.0.9 itself writes. Each expected
2
+ //! value was dumped from `nft --debug=mnl add set|map ...` with the host nft
3
+ //! 1.0.9 (libnftnl 1.2.6) on x86_64; userdata numbers are host byte order.
4
+ use super::*;
5
+
6
+ fn hex(text: &str) -> Vec<u8> {
7
+ let digits: String = text.split_whitespace().collect();
8
+ (0..digits.len())
9
+ .step_by(2)
10
+ .map(|at| u8::from_str_radix(&digits[at..at + 2], 16).unwrap())
11
+ .collect()
12
+ }
13
+
14
+ /// `typeof ip saddr . th dport . ct original ip saddr . ct zone`
15
+ #[test]
16
+ #[cfg(target_endian = "little")]
17
+ fn concatenated_key_types_and_userdata_are_nfts() {
18
+ let key = [IP_SADDR, TH_DPORT, CT_ORIGINAL_SADDR, CT_ZONE];
19
+ assert_eq!(datatype(&key), 0x001c_d1c4);
20
+ assert_eq!(key_length(&key), 16);
21
+ assert_eq!(
22
+ userdata(&key, None).unwrap(),
23
+ hex("00 04 00000000 03 60 00 04 0d000000 01 58
24
+ 00 14 00 04 07000000 01 0c 00 04 0c000000 01 04 0b000000
25
+ 01 14 00 04 07000000 01 0c 00 04 0b000000 01 04 02000000
26
+ 02 14 00 04 0c000000 01 0c 00 04 13000000 01 04 00000000
27
+ 03 14 00 04 0c000000 01 0c 00 04 11000000 01 04 ffffffff")
28
+ );
29
+ // `typeof meta oif . meta oifname . ct reply ip saddr . ct reply proto-src`
30
+ let key = [OIF, OIFNAME, CT_REPLY_SADDR, CT_REPLY_SPORT];
31
+ assert_eq!(datatype(&key), 0x0052_91cd);
32
+ assert_eq!(key_length(&key), 28);
33
+ assert_eq!(
34
+ userdata(&key, None).unwrap(),
35
+ hex("00 04 00000000 03 54 00 04 0d000000 01 4c
36
+ 00 0e 00 04 09000000 01 06 00 04 05000000
37
+ 01 0e 00 04 09000000 01 06 00 04 07000000
38
+ 02 14 00 04 0c000000 01 0c 00 04 13000000 01 04 01000000
39
+ 03 14 00 04 0c000000 01 0c 00 04 0b000000 01 04 01000000")
40
+ );
41
+ }
42
+
43
+ /// `typeof meta l4proto . ct protocol . ip daddr : ip saddr . th sport . th dport`
44
+ /// and `typeof ct zone : ct label`.
45
+ #[test]
46
+ #[cfg(target_endian = "little")]
47
+ fn map_data_types_and_userdata_are_nfts() {
48
+ let key = [L4PROTO, CT_PROTOCOL, IP_DADDR];
49
+ let data = [IP_SADDR, TH_SPORT, TH_DPORT];
50
+ assert_eq!(datatype(&key), 0xc307);
51
+ assert_eq!(datatype(&data), 0x734d);
52
+ assert_eq!((key_length(&key), data_length(&data)), (12, 12));
53
+ assert_eq!(
54
+ userdata(&key, Some(&data)).unwrap(),
55
+ hex("00 04 00000000 01 04 00000000
56
+ 03 44 00 04 0d000000 01 3c
57
+ 00 0e 00 04 09000000 01 06 00 04 10000000
58
+ 01 14 00 04 0c000000 01 0c 00 04 0a000000 01 04 ffffffff
59
+ 02 14 00 04 07000000 01 0c 00 04 0c000000 01 04 0c000000
60
+ 04 4a 00 04 0d000000 01 42
61
+ 00 14 00 04 07000000 01 0c 00 04 0c000000 01 04 0b000000
62
+ 01 14 00 04 07000000 01 0c 00 04 0b000000 01 04 01000000
63
+ 02 14 00 04 07000000 01 0c 00 04 0b000000 01 04 02000000
64
+ 06 04 00000000")
65
+ );
66
+ let (key, data) = ([CT_ZONE], [CT_LABEL]);
67
+ assert_eq!((datatype(&key), datatype(&data)), (4, 30));
68
+ assert_eq!(data_length(&data), 16);
69
+ assert_eq!(
70
+ userdata(&key, Some(&data)).unwrap(),
71
+ hex("00 04 01000000 01 04 01000000
72
+ 03 14 00 04 0c000000 01 0c 00 04 11000000 01 04 ffffffff
73
+ 04 14 00 04 0c000000 01 0c 00 04 0d000000 01 04 ffffffff
74
+ 06 04 00000000")
75
+ );
76
+ // nft declares the zone alone as its 2 bytes; this map keeps the zone
77
+ // padded to its register, as every release before typed sets did.
78
+ assert_eq!(key_length(&key), 4);
79
+ }
80
+
81
+ /// Beyond four fields nft 1.0.9 cannot describe a key by `typeof`: its
82
+ /// concatenation userdata stops at `NFT_REG32_SIZE` (4) entries, and the type
83
+ /// keeps 32 bits, six per field, dropping what a longer key shifts out.
84
+ #[test]
85
+ fn longer_keys_carry_their_type_and_byte_order_alone() {
86
+ let key = [IIF, L4PROTO, IP_SADDR, IP_DADDR, TH_DPORT];
87
+ assert_eq!(
88
+ datatype(&key),
89
+ (20 << 24) | (12 << 18) | (7 << 12) | (7 << 6) | 13
90
+ );
91
+ assert_eq!(userdata(&key, None).unwrap(), hex("00 04 00000000"));
92
+ let flow = [
93
+ IIF,
94
+ L4PROTO,
95
+ CT_PROTOCOL,
96
+ IP_SADDR,
97
+ IP_DADDR,
98
+ TH_DPORT,
99
+ CT_ORIGINAL_SADDR,
100
+ CT_ORIGINAL_DADDR,
101
+ CT_ORIGINAL_DPORT,
102
+ CT_ZONE,
103
+ ];
104
+ let truncated = flow.iter().fold(0_u64, |result, field| {
105
+ (result << 6) | u64::from(field.datatype)
106
+ });
107
+ assert_eq!(u64::from(datatype(&flow)), truncated & u64::from(u32::MAX));
108
+ let data = [IP_SADDR, TH_SPORT, TH_SPORT];
109
+ let mut expected = hex("00 04 00000000 01 04 00000000");
110
+ expected.extend(hex("04 4a 00 04 0d000000 01 42
111
+ 00 14 00 04 07000000 01 0c 00 04 0c000000 01 04 0b000000
112
+ 01 14 00 04 07000000 01 0c 00 04 0b000000 01 04 01000000
113
+ 02 14 00 04 07000000 01 0c 00 04 0b000000 01 04 01000000
114
+ 06 04 00000000"));
115
+ #[cfg(target_endian = "little")]
116
+ assert_eq!(userdata(&flow, Some(&data)).unwrap(), expected);
117
+ }
118
+
119
+ /// A lookup loads every field of the set's key, by length and type; the other
120
+ /// direction's field of the same kind fits, anything else refuses.
121
+ #[test]
122
+ fn lookups_must_fit_the_declared_key() {
123
+ let declared = [IIF, IIFNAME, IP_SADDR];
124
+ assert!(Loads::new()
125
+ .link(true)
126
+ .address(true)
127
+ .lookup("s", &declared)
128
+ .is_ok());
129
+ assert!(Loads::new()
130
+ .link(false)
131
+ .address(false)
132
+ .lookup("s", &declared)
133
+ .is_ok());
134
+ assert_eq!(
135
+ Loads::new().link(true).port(true).lookup("s", &declared),
136
+ Err(Error::Invalid)
137
+ );
138
+ assert_eq!(
139
+ Loads::new().link(true).lookup("s", &declared),
140
+ Err(Error::Invalid)
141
+ );
142
+ assert_eq!(
143
+ Loads::new()
144
+ .index(true)
145
+ .address(true)
146
+ .address(true)
147
+ .lookup("s", &declared),
148
+ Err(Error::Invalid)
149
+ );
150
+ }