@push.rocks/smartnftables 1.4.0 → 1.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.
Files changed (41) hide show
  1. package/changelog.md +14 -0
  2. package/dist_rust/smartnftables_linux_amd64_musl +0 -0
  3. package/dist_rust/smartnftables_linux_amd64_musl.tsrust-build.json +4 -4
  4. package/dist_rust/smartnftables_linux_arm64_musl +0 -0
  5. package/dist_rust/smartnftables_linux_arm64_musl.tsrust-build.json +4 -4
  6. package/dist_ts/00_commitinfo_data.js +1 -1
  7. package/dist_ts/classes.managednftables.d.ts +7 -7
  8. package/dist_ts/classes.managednftables.js +1 -1
  9. package/dist_ts/index.d.ts +1 -0
  10. package/dist_ts/index.js +1 -1
  11. package/dist_ts/managed.egress.types.d.ts +91 -0
  12. package/dist_ts/managed.egress.types.js +2 -0
  13. package/dist_ts/managed.types.d.ts +20 -18
  14. package/package.json +2 -2
  15. package/readme.md +75 -4
  16. package/rust/src/egress.compile.rs +288 -0
  17. package/rust/src/egress.host.rs +103 -0
  18. package/rust/src/egress.router.rs +231 -0
  19. package/rust/src/egress.rs +682 -0
  20. package/rust/src/egress_tests.rs +332 -0
  21. package/rust/src/main.rs +3 -1
  22. package/rust/src/managed.rs +105 -0
  23. package/rust/src/owner.rs +96 -6
  24. package/rust/src/owner_coexistence_tests.rs +77 -0
  25. package/rust/src/owner_egress_identity_tests.rs +155 -0
  26. package/rust/src/owner_egress_tests.rs +153 -0
  27. package/rust/src/owner_egress_traffic_tests.rs +470 -0
  28. package/rust/src/owner_host_traffic_tests.rs +305 -0
  29. package/rust/src/owner_identity_tests.rs +169 -0
  30. package/rust/src/owner_link_tests.rs +119 -0
  31. package/rust/src/owner_packet_fixture.rs +200 -0
  32. package/rust/src/owner_tests.rs +33 -7
  33. package/rust/src/policy.rs +120 -81
  34. package/rust/src/tests.rs +22 -0
  35. package/rust/src/wire.links.rs +268 -0
  36. package/rust/src/wire.rs +3 -64
  37. package/ts/00_commitinfo_data.ts +1 -1
  38. package/ts/classes.managednftables.ts +12 -12
  39. package/ts/index.ts +1 -0
  40. package/ts/managed.egress.types.ts +85 -0
  41. package/ts/managed.types.ts +20 -16
@@ -0,0 +1,200 @@
1
+ //! Guest-only Ethernet observation/injection for packet-path qualifications.
2
+ use super::*;
3
+ use std::os::fd::{AsRawFd, FromRawFd, OwnedFd};
4
+ use std::time::{Duration, Instant};
5
+
6
+ pub(super) struct PacketSocket {
7
+ fd: OwnedFd,
8
+ index: i32,
9
+ }
10
+ impl PacketSocket {
11
+ pub(super) fn open(name: &str) -> Self {
12
+ guest_guard();
13
+ let name = std::ffi::CString::new(name).unwrap();
14
+ let index = unsafe { libc::if_nametoindex(name.as_ptr()) } as i32;
15
+ assert!(index > 0);
16
+ let raw = unsafe {
17
+ libc::socket(
18
+ libc::AF_PACKET,
19
+ libc::SOCK_DGRAM | libc::SOCK_CLOEXEC,
20
+ i32::from((libc::ETH_P_IP as u16).to_be()),
21
+ )
22
+ };
23
+ assert!(
24
+ raw >= 0,
25
+ "packet socket: {}",
26
+ std::io::Error::last_os_error()
27
+ );
28
+ let fd = unsafe { OwnedFd::from_raw_fd(raw) };
29
+ let mut address: libc::sockaddr_ll = unsafe { std::mem::zeroed() };
30
+ address.sll_family = libc::AF_PACKET as u16;
31
+ address.sll_protocol = (libc::ETH_P_IP as u16).to_be();
32
+ address.sll_ifindex = index;
33
+ assert_eq!(
34
+ unsafe {
35
+ libc::bind(
36
+ raw,
37
+ (&address as *const libc::sockaddr_ll).cast(),
38
+ std::mem::size_of_val(&address) as _,
39
+ )
40
+ },
41
+ 0
42
+ );
43
+ Self { fd, index }
44
+ }
45
+ pub(super) fn send(&self, mac: [u8; 6], packet: &[u8]) {
46
+ let mut address: libc::sockaddr_ll = unsafe { std::mem::zeroed() };
47
+ address.sll_family = libc::AF_PACKET as u16;
48
+ address.sll_protocol = (libc::ETH_P_IP as u16).to_be();
49
+ address.sll_ifindex = self.index;
50
+ address.sll_halen = 6;
51
+ address.sll_addr[..6].copy_from_slice(&mac);
52
+ assert_eq!(
53
+ unsafe {
54
+ libc::sendto(
55
+ self.fd.as_raw_fd(),
56
+ packet.as_ptr().cast(),
57
+ packet.len(),
58
+ 0,
59
+ (&address as *const libc::sockaddr_ll).cast(),
60
+ std::mem::size_of_val(&address) as _,
61
+ )
62
+ },
63
+ packet.len() as isize
64
+ );
65
+ }
66
+ pub(super) fn matching(&self, mut matches: impl FnMut(&[u8]) -> bool) -> Option<Vec<u8>> {
67
+ let deadline = Instant::now() + Duration::from_millis(500);
68
+ loop {
69
+ let remaining = deadline
70
+ .saturating_duration_since(Instant::now())
71
+ .as_millis();
72
+ if remaining == 0 {
73
+ return None;
74
+ }
75
+ let mut poll = libc::pollfd {
76
+ fd: self.fd.as_raw_fd(),
77
+ events: libc::POLLIN,
78
+ revents: 0,
79
+ };
80
+ let ready = unsafe { libc::poll(&mut poll, 1, remaining as i32) };
81
+ assert!(ready >= 0);
82
+ if ready == 0 {
83
+ return None;
84
+ }
85
+ let mut packet = vec![0; 65536];
86
+ let size = unsafe {
87
+ libc::recv(
88
+ self.fd.as_raw_fd(),
89
+ packet.as_mut_ptr().cast(),
90
+ packet.len(),
91
+ libc::MSG_TRUNC,
92
+ )
93
+ };
94
+ assert!(size >= 0 && (size as usize) <= packet.len());
95
+ packet.truncate(size as usize);
96
+ if matches(&packet) {
97
+ return Some(packet);
98
+ }
99
+ }
100
+ }
101
+ }
102
+ pub(super) fn mac(name: &str) -> [u8; 6] {
103
+ let output = std::process::Command::new("/bin/ip")
104
+ .args(["-j", "link", "show", "dev", name])
105
+ .output()
106
+ .unwrap();
107
+ assert!(output.status.success());
108
+ let value = serde_json::from_slice::<serde_json::Value>(&output.stdout).unwrap();
109
+ value[0]["address"]
110
+ .as_str()
111
+ .unwrap()
112
+ .split(':')
113
+ .map(|byte| u8::from_str_radix(byte, 16).unwrap())
114
+ .collect::<Vec<_>>()
115
+ .try_into()
116
+ .unwrap()
117
+ }
118
+ fn checksum(bytes: &[u8]) -> u16 {
119
+ let mut sum: u32 = bytes
120
+ .chunks(2)
121
+ .map(|bytes| u32::from(bytes[0]) * 256 + u32::from(*bytes.get(1).unwrap_or(&0)))
122
+ .sum();
123
+ while sum >> 16 != 0 {
124
+ sum = (sum & 0xffff) + (sum >> 16);
125
+ }
126
+ !(sum as u16)
127
+ }
128
+ pub(super) fn tcp(
129
+ source: [u8; 4],
130
+ destination: [u8; 4],
131
+ source_port: u16,
132
+ destination_port: u16,
133
+ flags: u8,
134
+ ) -> Vec<u8> {
135
+ let mut packet = vec![0; 40];
136
+ packet[0] = 0x45;
137
+ packet[2..4].copy_from_slice(&40_u16.to_be_bytes());
138
+ packet[8] = 64;
139
+ packet[9] = 6;
140
+ packet[12..16].copy_from_slice(&source);
141
+ packet[16..20].copy_from_slice(&destination);
142
+ let sum = checksum(&packet[..20]);
143
+ packet[10..12].copy_from_slice(&sum.to_be_bytes());
144
+ packet[20..22].copy_from_slice(&source_port.to_be_bytes());
145
+ packet[22..24].copy_from_slice(&destination_port.to_be_bytes());
146
+ packet[24..28].copy_from_slice(&100_u32.to_be_bytes());
147
+ packet[32] = 0x50;
148
+ packet[33] = flags;
149
+ packet[34..36].copy_from_slice(&65535_u16.to_be_bytes());
150
+ let mut pseudo = Vec::new();
151
+ pseudo.extend(source);
152
+ pseudo.extend(destination);
153
+ pseudo.extend([0, 6, 0, 20]);
154
+ pseudo.extend(&packet[20..]);
155
+ let sum = checksum(&pseudo);
156
+ packet[36..38].copy_from_slice(&sum.to_be_bytes());
157
+ packet
158
+ }
159
+
160
+ pub(super) fn udp(
161
+ source: [u8; 4],
162
+ destination: [u8; 4],
163
+ source_port: u16,
164
+ destination_port: u16,
165
+ ) -> Vec<u8> {
166
+ let mut packet = vec![0; 32];
167
+ packet[0] = 0x45;
168
+ packet[2..4].copy_from_slice(&32_u16.to_be_bytes());
169
+ packet[8] = 64;
170
+ packet[9] = 17;
171
+ packet[12..16].copy_from_slice(&source);
172
+ packet[16..20].copy_from_slice(&destination);
173
+ let sum = checksum(&packet[..20]);
174
+ packet[10..12].copy_from_slice(&sum.to_be_bytes());
175
+ packet[20..22].copy_from_slice(&source_port.to_be_bytes());
176
+ packet[22..24].copy_from_slice(&destination_port.to_be_bytes());
177
+ packet[24..26].copy_from_slice(&12_u16.to_be_bytes());
178
+ packet[28..].copy_from_slice(b"frag");
179
+ packet
180
+ }
181
+ pub(super) fn fragments(packet: &[u8], id: u16) -> [Vec<u8>; 2] {
182
+ assert_eq!(packet.len(), 32);
183
+ assert_eq!(packet[0], 0x45);
184
+ let fragment = |body: &[u8], offset: u16| {
185
+ let mut result = packet[..20].to_vec();
186
+ result.extend(body);
187
+ let length = result.len() as u16;
188
+ result[2..4].copy_from_slice(&length.to_be_bytes());
189
+ result[4..6].copy_from_slice(&id.to_be_bytes());
190
+ result[6..8].copy_from_slice(&offset.to_be_bytes());
191
+ result[10..12].fill(0);
192
+ let sum = checksum(&result[..20]);
193
+ result[10..12].copy_from_slice(&sum.to_be_bytes());
194
+ result
195
+ };
196
+ [
197
+ fragment(&packet[20..28], 0x2000),
198
+ fragment(&packet[28..], 1),
199
+ ]
200
+ }
@@ -3,7 +3,28 @@ use super::*;
3
3
  #[path = "owner_namespace_tests.rs"]
4
4
  mod namespace_tests;
5
5
 
6
- fn isolated() {
6
+ #[path = "owner_egress_identity_tests.rs"]
7
+ mod egress_identity_tests;
8
+
9
+ #[path = "owner_link_tests.rs"]
10
+ mod link_tests;
11
+
12
+ #[path = "owner_egress_tests.rs"]
13
+ mod egress_tests;
14
+
15
+ #[path = "owner_egress_traffic_tests.rs"]
16
+ mod egress_traffic_tests;
17
+
18
+ #[path = "owner_packet_fixture.rs"]
19
+ mod packet_fixture;
20
+
21
+ #[path = "owner_host_traffic_tests.rs"]
22
+ mod host_traffic_tests;
23
+
24
+ #[path = "owner_coexistence_tests.rs"]
25
+ mod coexistence_tests;
26
+
27
+ fn guest_guard() {
7
28
  assert_eq!(
8
29
  std::env::var("SMARTNFTABLES_ISOLATED_NATIVE").as_deref(),
9
30
  Ok("qemu-6.18.35")
@@ -12,6 +33,10 @@ fn isolated() {
12
33
  .unwrap()
13
34
  .contains("smartnftables_qualification=1"));
14
35
  assert_eq!(unsafe { libc::geteuid() }, 0);
36
+ }
37
+
38
+ fn isolated() {
39
+ guest_guard();
15
40
  let initial = context().unwrap();
16
41
  assert_eq!(
17
42
  unsafe { libc::unshare(libc::CLONE_NEWNET | libc::CLONE_NEWNS) },
@@ -279,7 +304,7 @@ fn native_interface_ipv4_directed_grants_and_spoof_denial() {
279
304
  assert_eq!(
280
305
  owner.reconcile(Transition {
281
306
  previous: None,
282
- target: invalid_pair.prepare().unwrap()
307
+ target: invalid_pair.prepare().unwrap().into()
283
308
  }),
284
309
  Err(Error::Conflict)
285
310
  );
@@ -289,7 +314,7 @@ fn native_interface_ipv4_directed_grants_and_spoof_denial() {
289
314
  assert_eq!(
290
315
  owner.reconcile(Transition {
291
316
  previous: None,
292
- target: policy.clone().prepare().unwrap()
317
+ target: policy.clone().prepare().unwrap().into()
293
318
  }),
294
319
  Err(Error::Conflict)
295
320
  );
@@ -298,7 +323,7 @@ fn native_interface_ipv4_directed_grants_and_spoof_denial() {
298
323
  let current = owner
299
324
  .reconcile(Transition {
300
325
  previous: None,
301
- target: policy.clone().prepare().unwrap(),
326
+ target: policy.clone().prepare().unwrap().into(),
302
327
  })
303
328
  .expect("apply interface policy");
304
329
  assert!(owner.inspect().enforced);
@@ -336,7 +361,7 @@ fn native_interface_ipv4_directed_grants_and_spoof_denial() {
336
361
  let revoked = owner
337
362
  .reconcile(Transition {
338
363
  previous: Some(current),
339
- target: denied.prepare().unwrap(),
364
+ target: denied.prepare().unwrap().into(),
340
365
  })
341
366
  .expect("atomic complete deny");
342
367
  udp(&client, &server, false);
@@ -364,6 +389,7 @@ fn empty(revision: u64) -> Prepared {
364
389
  }
365
390
  .prepare()
366
391
  .unwrap()
392
+ .into()
367
393
  }
368
394
 
369
395
  #[test]
@@ -699,14 +725,14 @@ fn native_tun_forwarding_binds_remote_ingress_and_rejects_tap() {
699
725
  assert_eq!(
700
726
  owner.reconcile(Transition {
701
727
  previous: None,
702
- target: invalid.prepare().unwrap()
728
+ target: invalid.prepare().unwrap().into()
703
729
  }),
704
730
  Err(Error::Conflict)
705
731
  );
706
732
  let applied = owner
707
733
  .reconcile(Transition {
708
734
  previous: None,
709
- target: policy.prepare().unwrap(),
735
+ target: policy.prepare().unwrap().into(),
710
736
  })
711
737
  .unwrap();
712
738
  send_tunnel(&mut device, true);
@@ -210,10 +210,10 @@ impl Policy {
210
210
  }
211
211
  }
212
212
 
213
- fn expr(name: &str, data: Vec<Attr>) -> Attr {
213
+ pub(crate) fn expr(name: &str, data: Vec<Attr>) -> Attr {
214
214
  Attr::nested(1, vec![Attr::string(1, name), Attr::nested(2, data)])
215
215
  }
216
- fn compare(value: Vec<u8>) -> Attr {
216
+ pub(crate) fn compare(value: Vec<u8>) -> Attr {
217
217
  expr(
218
218
  "cmp",
219
219
  vec![
@@ -223,7 +223,7 @@ fn compare(value: Vec<u8>) -> Attr {
223
223
  ],
224
224
  )
225
225
  }
226
- fn meta(key: u32, value: Vec<u8>) -> Vec<Attr> {
226
+ pub(crate) fn meta(key: u32, value: Vec<u8>) -> Vec<Attr> {
227
227
  vec![
228
228
  expr("meta", vec![Attr::u32(1, 1), Attr::u32(2, key)]),
229
229
  compare(value),
@@ -239,7 +239,7 @@ fn interface(endpoint: &Endpoint, incoming: bool) -> Vec<Attr> {
239
239
  result.extend(meta(if incoming { 6 } else { 7 }, name));
240
240
  result
241
241
  }
242
- fn ipv4() -> Vec<Attr> {
242
+ pub(crate) fn ipv4() -> Vec<Attr> {
243
243
  meta(15, vec![libc::NFPROTO_IPV4 as u8])
244
244
  }
245
245
  fn address(cidr: &str, source: bool) -> Result<Vec<Attr>> {
@@ -268,7 +268,7 @@ fn address(cidr: &str, source: bool) -> Result<Vec<Attr>> {
268
268
  compare(address.to_be_bytes().to_vec()),
269
269
  ])
270
270
  }
271
- fn verdict(code: i32, chain: Option<&str>) -> Attr {
271
+ pub(crate) fn verdict(code: i32, chain: Option<&str>) -> Attr {
272
272
  let mut data = vec![Attr::u32(1, code as u32)];
273
273
  if let Some(chain) = chain {
274
274
  data.push(Attr::string(2, chain));
@@ -329,99 +329,138 @@ impl Prepared {
329
329
  ],
330
330
  ));
331
331
  }
332
+ for (name, expressions) in self.guard_chains()? {
333
+ program.push((3, vec![Attr::string(1, table), Attr::string(3, &name)]));
334
+ for expressions in expressions {
335
+ rule(&name, expressions, &mut program);
336
+ }
337
+ }
338
+ for chain in ["input", "forward", "output"] {
339
+ let parts = self.filter_rules(chain)?;
340
+ for expressions in parts
341
+ .guards
342
+ .into_iter()
343
+ .chain(parts.grants)
344
+ .chain(parts.denials)
345
+ {
346
+ rule(chain, expressions, &mut program);
347
+ }
348
+ }
349
+ Ok(program)
350
+ }
351
+ }
352
+
353
+ /// Reused inside a combined router graph; order remains part of schema-v1's bytes.
354
+ pub(crate) struct PrivateRules {
355
+ pub guards: Vec<Vec<Attr>>,
356
+ pub grants: Vec<Vec<Attr>>,
357
+ pub denials: Vec<Vec<Attr>>,
358
+ }
359
+ impl Prepared {
360
+ pub(crate) fn guard_chains(&self) -> Result<Vec<(String, Vec<Vec<Attr>>)>> {
361
+ let mut result = Vec::new();
332
362
  for (index, endpoint) in self.policy.endpoints.iter().enumerate() {
333
363
  for incoming in [true, false] {
334
364
  let name = format!("guard_{index}_{}", if incoming { "in" } else { "out" });
335
- program.push((3, vec![Attr::string(1, table), Attr::string(3, &name)]));
365
+ let mut rules = Vec::new();
336
366
  for cidr in &endpoint.source_prefixes {
337
367
  let mut expressions = ipv4();
338
368
  expressions.extend(address(cidr, incoming)?);
339
369
  expressions.push(verdict(-5, None));
340
- rule(&name, expressions, &mut program);
370
+ rules.push(expressions);
341
371
  }
342
- rule(&name, vec![verdict(0, None)], &mut program);
372
+ rules.push(vec![verdict(0, None)]);
373
+ result.push((name, rules));
343
374
  }
344
375
  }
345
- for chain in ["input", "forward", "output"] {
346
- for (index, endpoint) in self.policy.endpoints.iter().enumerate() {
347
- for incoming in [true, false] {
348
- if (chain == "input" && !incoming) || (chain == "output" && incoming) {
349
- continue;
350
- }
351
- let name = format!("guard_{index}_{}", if incoming { "in" } else { "out" });
352
- let mut expressions = interface(endpoint, incoming);
353
- expressions.push(verdict(-3, Some(&name)));
354
- rule(chain, expressions, &mut program);
355
- }
356
- }
357
- for grant in &self.policy.rules {
358
- if chain == "input" && grant.destination_endpoint.is_some()
359
- || chain == "output" && grant.source_endpoint.is_some()
360
- || chain == "forward"
361
- && (grant.source_endpoint.is_none() || grant.destination_endpoint.is_none())
362
- {
376
+ Ok(result)
377
+ }
378
+ pub(crate) fn filter_rules(&self, chain: &str) -> Result<PrivateRules> {
379
+ if !["input", "forward", "output"].contains(&chain) {
380
+ return Err(Error::Invalid);
381
+ }
382
+ let mut result = PrivateRules {
383
+ guards: Vec::new(),
384
+ grants: Vec::new(),
385
+ denials: Vec::new(),
386
+ };
387
+ for (index, endpoint) in self.policy.endpoints.iter().enumerate() {
388
+ for incoming in [true, false] {
389
+ if (chain == "input" && !incoming) || (chain == "output" && incoming) {
363
390
  continue;
364
391
  }
365
- let mut expressions = ipv4();
366
- for (id, incoming) in [
367
- (&grant.source_endpoint, true),
368
- (&grant.destination_endpoint, false),
369
- ] {
370
- if let Some(id) = id {
371
- let endpoint = self
372
- .policy
373
- .endpoints
374
- .iter()
375
- .find(|ep| &ep.id == id)
376
- .ok_or(Error::Invalid)?;
377
- expressions.extend(interface(endpoint, incoming));
378
- }
379
- }
380
- expressions.extend(address(&grant.source_prefix, true)?);
381
- expressions.extend(address(&grant.destination_prefix, false)?);
382
- if grant.protocol != "any" {
383
- expressions
384
- .extend(meta(16, vec![if grant.protocol == "tcp" { 6 } else { 17 }]));
392
+ let name = format!("guard_{index}_{}", if incoming { "in" } else { "out" });
393
+ let mut expressions = interface(endpoint, incoming);
394
+ expressions.push(verdict(-3, Some(&name)));
395
+ result.guards.push(expressions);
396
+ }
397
+ }
398
+ for grant in &self.policy.rules {
399
+ if chain == "input" && grant.destination_endpoint.is_some()
400
+ || chain == "output" && grant.source_endpoint.is_some()
401
+ || chain == "forward"
402
+ && (grant.source_endpoint.is_none() || grant.destination_endpoint.is_none())
403
+ {
404
+ continue;
405
+ }
406
+ let mut expressions = ipv4();
407
+ for (id, incoming) in [
408
+ (&grant.source_endpoint, true),
409
+ (&grant.destination_endpoint, false),
410
+ ] {
411
+ if let Some(id) = id {
412
+ let endpoint = self
413
+ .policy
414
+ .endpoints
415
+ .iter()
416
+ .find(|ep| &ep.id == id)
417
+ .ok_or(Error::Invalid)?;
418
+ expressions.extend(interface(endpoint, incoming));
385
419
  }
386
- for (port, offset) in [(grant.source_port, 0), (grant.destination_port, 2)] {
387
- if let Some(port) = port {
388
- expressions.push(expr(
389
- "payload",
390
- vec![
391
- Attr::u32(1, 1),
392
- Attr::u32(2, 2),
393
- Attr::u32(3, offset),
394
- Attr::u32(4, 2),
395
- ],
396
- ));
397
- expressions.push(compare(port.to_be_bytes().to_vec()));
398
- }
420
+ }
421
+ expressions.extend(address(&grant.source_prefix, true)?);
422
+ expressions.extend(address(&grant.destination_prefix, false)?);
423
+ if grant.protocol != "any" {
424
+ expressions.extend(meta(16, vec![if grant.protocol == "tcp" { 6 } else { 17 }]));
425
+ }
426
+ for (port, offset) in [(grant.source_port, 0), (grant.destination_port, 2)] {
427
+ if let Some(port) = port {
428
+ expressions.push(expr(
429
+ "payload",
430
+ vec![
431
+ Attr::u32(1, 1),
432
+ Attr::u32(2, 2),
433
+ Attr::u32(3, offset),
434
+ Attr::u32(4, 2),
435
+ ],
436
+ ));
437
+ expressions.push(compare(port.to_be_bytes().to_vec()));
399
438
  }
400
- expressions.push(verdict(1, None));
401
- rule(chain, expressions, &mut program);
402
439
  }
403
- for endpoint in &self.policy.endpoints {
404
- for incoming in [true, false] {
405
- if (chain == "input" && !incoming) || (chain == "output" && incoming) {
406
- continue;
407
- }
408
- // Either selector matching is enough to retain denial after rename/reuse.
409
- for mut expressions in [
410
- meta(
411
- if incoming { 4 } else { 5 },
412
- endpoint.interface_index.to_ne_bytes().to_vec(),
413
- ),
414
- meta(
415
- if incoming { 6 } else { 7 },
416
- [endpoint.interface_name.as_bytes(), &[0]].concat(),
417
- ),
418
- ] {
419
- expressions.push(verdict(0, None));
420
- rule(chain, expressions, &mut program);
421
- }
440
+ expressions.push(verdict(1, None));
441
+ result.grants.push(expressions);
442
+ }
443
+ for endpoint in &self.policy.endpoints {
444
+ for incoming in [true, false] {
445
+ if (chain == "input" && !incoming) || (chain == "output" && incoming) {
446
+ continue;
447
+ }
448
+ // Either selector matching is enough to retain denial after rename/reuse.
449
+ for mut expressions in [
450
+ meta(
451
+ if incoming { 4 } else { 5 },
452
+ endpoint.interface_index.to_ne_bytes().to_vec(),
453
+ ),
454
+ meta(
455
+ if incoming { 6 } else { 7 },
456
+ [endpoint.interface_name.as_bytes(), &[0]].concat(),
457
+ ),
458
+ ] {
459
+ expressions.push(verdict(0, None));
460
+ result.denials.push(expressions);
422
461
  }
423
462
  }
424
463
  }
425
- Ok(program)
464
+ Ok(result)
426
465
  }
427
466
  }
package/rust/src/tests.rs CHANGED
@@ -1,9 +1,31 @@
1
1
  use crate::{decode, policy::Policy, wire, Error};
2
+ use sha2::{Digest, Sha256};
2
3
 
3
4
  fn policy() -> Policy {
4
5
  serde_json::from_str(r#"{"schemaVersion":1,"revision":1,"endpoints":[{"id":"a","interfaceIndex":2,"interfaceName":"worka","interfaceKind":"veth","sourcePrefixes":["10.81.0.2/32"]},{"id":"b","interfaceIndex":3,"interfaceName":"workb","interfaceKind":"veth","sourcePrefixes":["10.81.0.3/32"]}],"rules":[{"sourceEndpoint":"a","destinationEndpoint":"b","sourcePrefix":"10.81.0.2/32","destinationPrefix":"10.81.0.3/32","protocol":"udp","sourcePort":null,"destinationPort":53}]}"#).unwrap()
5
6
  }
6
7
 
8
+ #[test]
9
+ fn v1_policy_and_program_bytes_remain_frozen() {
10
+ let prepared = policy().prepare().unwrap();
11
+ let mut bytes = Vec::new();
12
+ for (kind, attributes) in prepared.program("snft_v1_golden").unwrap() {
13
+ let encoded = wire::encode_attrs(&attributes);
14
+ bytes.extend(kind.to_be_bytes());
15
+ bytes.extend((encoded.len() as u32).to_be_bytes());
16
+ bytes.extend(encoded);
17
+ }
18
+ assert_eq!(
19
+ prepared.digest,
20
+ "sha256:13897f4e711db2111618c61eb3f0be00714ed79f2075bf7f75e16f93f26ce2db"
21
+ );
22
+ assert_eq!(bytes.len(), 11020);
23
+ assert_eq!(
24
+ format!("{:x}", Sha256::digest(bytes)),
25
+ "e57215884bca4bc8e0d7bef4c975cc3a1cc9e9233e44c248502a9efd99fa5c34"
26
+ );
27
+ }
28
+
7
29
  #[test]
8
30
  fn canonical_policy_rejects_ambiguous_or_unbound_grants() {
9
31
  let prepared = policy().prepare().unwrap();