@push.rocks/smartnftables 1.3.1 → 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 (43) hide show
  1. package/changelog.md +24 -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 +9 -3
  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 +25 -19
  14. package/package.json +5 -5
  15. package/readme.md +97 -3
  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 +23 -3
  22. package/rust/src/managed.rs +105 -0
  23. package/rust/src/namespace.rs +51 -0
  24. package/rust/src/owner.rs +96 -6
  25. package/rust/src/owner_coexistence_tests.rs +77 -0
  26. package/rust/src/owner_egress_identity_tests.rs +155 -0
  27. package/rust/src/owner_egress_tests.rs +153 -0
  28. package/rust/src/owner_egress_traffic_tests.rs +470 -0
  29. package/rust/src/owner_host_traffic_tests.rs +305 -0
  30. package/rust/src/owner_identity_tests.rs +169 -0
  31. package/rust/src/owner_link_tests.rs +119 -0
  32. package/rust/src/owner_namespace_tests.rs +242 -0
  33. package/rust/src/owner_packet_fixture.rs +200 -0
  34. package/rust/src/owner_tests.rs +36 -7
  35. package/rust/src/policy.rs +120 -81
  36. package/rust/src/tests.rs +22 -0
  37. package/rust/src/wire.links.rs +268 -0
  38. package/rust/src/wire.rs +3 -64
  39. package/ts/00_commitinfo_data.ts +1 -1
  40. package/ts/classes.managednftables.ts +19 -14
  41. package/ts/index.ts +1 -0
  42. package/ts/managed.egress.types.ts +85 -0
  43. package/ts/managed.types.ts +24 -16
@@ -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();
@@ -0,0 +1,268 @@
1
+ use super::{attrs, one, text, Attr, Socket};
2
+ use crate::{Error, Result};
3
+ use std::collections::BTreeSet;
4
+ use std::net::Ipv4Addr;
5
+ use std::time::{Duration, Instant};
6
+
7
+ struct Link {
8
+ hardware: u16,
9
+ flags: u32,
10
+ attributes: Vec<Attr>,
11
+ }
12
+
13
+ /// Current local facts only. Peer namespaces and link-generation lifetimes remain caller-owned.
14
+ #[derive(Clone, Copy)]
15
+ pub struct BoundInterface<'a> {
16
+ pub index: u32,
17
+ pub name: &'a str,
18
+ pub kind: &'a str,
19
+ pub mac: Option<&'a str>,
20
+ pub link_index: u32,
21
+ pub addresses: &'a [String],
22
+ }
23
+
24
+ fn read_link(socket: &mut Socket, index: u32) -> Result<Link> {
25
+ let sequence = socket.sequence()?;
26
+ let mut request = Vec::new();
27
+ request.extend(32_u32.to_ne_bytes());
28
+ request.extend(18_u16.to_ne_bytes());
29
+ request.extend(1_u16.to_ne_bytes());
30
+ request.extend(sequence.to_ne_bytes());
31
+ request.extend(socket.port.to_ne_bytes());
32
+ request.extend([0; 4]);
33
+ request.extend(index.to_ne_bytes());
34
+ request.extend([0; 8]);
35
+ socket.send(&request)?;
36
+ let until = Instant::now() + Duration::from_secs(3);
37
+ loop {
38
+ for message in socket.receive(until)? {
39
+ if message.sequence != sequence {
40
+ continue;
41
+ }
42
+ if message.kind == 2 {
43
+ let error = i32::from_ne_bytes(
44
+ message
45
+ .payload
46
+ .get(..4)
47
+ .ok_or(Error::Protocol)?
48
+ .try_into()
49
+ .unwrap(),
50
+ );
51
+ if error != 0 {
52
+ return Err(Error::kernel(error));
53
+ }
54
+ } else if message.kind == 16 && message.payload.len() >= 16 {
55
+ if u32::from_ne_bytes(message.payload[4..8].try_into().unwrap()) != index {
56
+ return Err(Error::Conflict);
57
+ }
58
+ return Ok(Link {
59
+ hardware: u16::from_ne_bytes(message.payload[2..4].try_into().unwrap()),
60
+ flags: u32::from_ne_bytes(message.payload[8..12].try_into().unwrap()),
61
+ attributes: attrs(&message.payload[16..])?,
62
+ });
63
+ } else {
64
+ return Err(Error::Protocol);
65
+ }
66
+ }
67
+ }
68
+ }
69
+ fn unbridged(attributes: &[Attr]) -> Result<()> {
70
+ if attributes.iter().any(|attr| attr.id() == 10)
71
+ && one(attributes, 10)?.value != 0_u32.to_ne_bytes()
72
+ {
73
+ return Err(Error::Conflict);
74
+ }
75
+ Ok(())
76
+ }
77
+ fn virtual_kind(attributes: &[Attr], kind: &str) -> Result<()> {
78
+ let info = attrs(&one(attributes, 18)?.value)?;
79
+ if text(&info, 1)? != kind {
80
+ return Err(Error::Conflict);
81
+ }
82
+ if kind == "tun" && one(&attrs(&one(&info, 2)?.value)?, 3)?.value != [1] {
83
+ return Err(Error::Conflict);
84
+ }
85
+ Ok(())
86
+ }
87
+ /// Preserve schema-v1's local name/index/kind/master checks exactly.
88
+ pub fn verify_interfaces<'a>(
89
+ interfaces: impl Iterator<Item = (u32, &'a str, &'a str)>,
90
+ ) -> Result<()> {
91
+ let mut socket = Socket::open_protocol(libc::NETLINK_ROUTE)?;
92
+ for (index, name, kind) in interfaces {
93
+ let link = read_link(&mut socket, index)?;
94
+ if text(&link.attributes, 3)? != name {
95
+ return Err(Error::Conflict);
96
+ }
97
+ unbridged(&link.attributes)?;
98
+ virtual_kind(&link.attributes, kind)?;
99
+ }
100
+ Ok(())
101
+ }
102
+ fn matches(link: &Link, binding: &BoundInterface<'_>) -> Result<()> {
103
+ if text(&link.attributes, 3)? != binding.name || link.flags & libc::IFF_LOOPBACK as u32 != 0 {
104
+ return Err(Error::Conflict);
105
+ }
106
+ unbridged(&link.attributes)?;
107
+ match binding.kind {
108
+ "veth" | "tun" => virtual_kind(&link.attributes, binding.kind)?,
109
+ "ethernet" => {
110
+ // Physical/VirtIO Ethernet has no IFLA_LINKINFO; bridge, VLAN, bond,
111
+ // dummy and other virtual kinds are not an implicit uplink role.
112
+ if link.attributes.iter().any(|item| item.id() == 18) {
113
+ return Err(Error::Conflict);
114
+ }
115
+ }
116
+ _ => return Err(Error::Invalid),
117
+ }
118
+ if binding.kind == "tun" {
119
+ if link.hardware != 65534 || binding.mac.is_some() {
120
+ return Err(Error::Conflict);
121
+ }
122
+ } else {
123
+ let mac = &one(&link.attributes, 1)?.value;
124
+ if link.hardware != 1
125
+ || mac.len() != 6
126
+ || mac[0] & 1 != 0
127
+ || mac.iter().all(|item| *item == 0)
128
+ {
129
+ return Err(Error::Conflict);
130
+ }
131
+ let actual = mac
132
+ .iter()
133
+ .map(|item| format!("{item:02x}"))
134
+ .collect::<Vec<_>>()
135
+ .join(":");
136
+ if binding.mac != Some(actual.as_str()) {
137
+ return Err(Error::Conflict);
138
+ }
139
+ }
140
+ let peer = if link.attributes.iter().any(|item| item.id() == 5) {
141
+ u32::from_ne_bytes(
142
+ one(&link.attributes, 5)?
143
+ .value
144
+ .as_slice()
145
+ .try_into()
146
+ .map_err(|_| Error::Protocol)?,
147
+ )
148
+ } else {
149
+ binding.index
150
+ };
151
+ if peer != binding.link_index {
152
+ return Err(Error::Conflict);
153
+ }
154
+ Ok(())
155
+ }
156
+ fn read_addresses(socket: &mut Socket, index: u32) -> Result<BTreeSet<String>> {
157
+ let sequence = socket.sequence()?;
158
+ let mut request = Vec::new();
159
+ request.extend(24_u32.to_ne_bytes());
160
+ request.extend(22_u16.to_ne_bytes());
161
+ request.extend(0x301_u16.to_ne_bytes());
162
+ request.extend(sequence.to_ne_bytes());
163
+ request.extend(socket.port.to_ne_bytes());
164
+ request.extend([libc::AF_INET as u8, 0, 0, 0]);
165
+ request.extend(index.to_ne_bytes());
166
+ socket.send(&request)?;
167
+ let until = Instant::now() + Duration::from_secs(3);
168
+ let mut result = BTreeSet::new();
169
+ let mut total = 0;
170
+ let mut count = 0;
171
+ loop {
172
+ for message in socket.receive(until)? {
173
+ if message.sequence != sequence {
174
+ continue;
175
+ }
176
+ if message.flags & 0x10 != 0 {
177
+ return Err(Error::Conflict);
178
+ }
179
+ if message.kind == 2 || message.kind == 3 {
180
+ if message.kind == 2 || !message.payload.is_empty() {
181
+ let error = i32::from_ne_bytes(
182
+ message
183
+ .payload
184
+ .get(..4)
185
+ .ok_or(Error::Protocol)?
186
+ .try_into()
187
+ .unwrap(),
188
+ );
189
+ if error != 0 {
190
+ return Err(Error::kernel(error));
191
+ }
192
+ }
193
+ if message.kind == 3 {
194
+ return Ok(result);
195
+ }
196
+ continue;
197
+ }
198
+ count += 1;
199
+ total += message.payload.len();
200
+ if message.kind != 20 || message.payload.len() < 8 || count > 4096 || total > 2_097_152
201
+ {
202
+ return Err(Error::Protocol);
203
+ }
204
+ if message.payload[0] != libc::AF_INET as u8 {
205
+ return Err(Error::Protocol);
206
+ }
207
+ if u32::from_ne_bytes(message.payload[4..8].try_into().unwrap()) != index {
208
+ continue;
209
+ }
210
+ let data = attrs(&message.payload[8..])?;
211
+ let local = if data.iter().any(|item| item.id() == 2) {
212
+ one(&data, 2)?
213
+ } else {
214
+ one(&data, 1)?
215
+ };
216
+ let bytes: [u8; 4] = local
217
+ .value
218
+ .as_slice()
219
+ .try_into()
220
+ .map_err(|_| Error::Protocol)?;
221
+ let flags = if data.iter().any(|item| item.id() == 8) {
222
+ u32::from_ne_bytes(
223
+ one(&data, 8)?
224
+ .value
225
+ .as_slice()
226
+ .try_into()
227
+ .map_err(|_| Error::Protocol)?,
228
+ )
229
+ } else {
230
+ u32::from(message.payload[2])
231
+ };
232
+ // Tentative, failed or deprecated source addresses do not qualify.
233
+ if flags & (0x40 | 0x08 | 0x20) != 0 {
234
+ continue;
235
+ }
236
+ if data.iter().any(|item| item.id() == 6) {
237
+ let cache = &one(&data, 6)?.value;
238
+ if cache.len() != 16 {
239
+ return Err(Error::Protocol);
240
+ }
241
+ if u32::from_ne_bytes(cache[..4].try_into().unwrap()) == 0
242
+ || u32::from_ne_bytes(cache[4..8].try_into().unwrap()) == 0
243
+ {
244
+ continue;
245
+ }
246
+ }
247
+ result.insert(Ipv4Addr::from(bytes).to_string());
248
+ }
249
+ }
250
+ }
251
+ pub fn verify_bound_interfaces<'a>(
252
+ interfaces: impl Iterator<Item = BoundInterface<'a>>,
253
+ ) -> Result<()> {
254
+ let mut socket = Socket::open_protocol(libc::NETLINK_ROUTE)?;
255
+ for binding in interfaces {
256
+ matches(&read_link(&mut socket, binding.index)?, &binding)?;
257
+ let addresses = read_addresses(&mut socket, binding.index)?;
258
+ if binding
259
+ .addresses
260
+ .iter()
261
+ .any(|address| !addresses.contains(address))
262
+ {
263
+ return Err(Error::Conflict);
264
+ }
265
+ matches(&read_link(&mut socket, binding.index)?, &binding)?;
266
+ }
267
+ Ok(())
268
+ }
package/rust/src/wire.rs CHANGED
@@ -123,70 +123,9 @@ pub struct Socket {
123
123
  sequence: u32,
124
124
  }
125
125
 
126
- /// Read-only, namespace-local RTM_GETLINK qualification. This is not a public
127
- /// routing or link-creation escape; only the strict native compiler calls it.
128
- pub fn verify_interfaces<'a>(
129
- interfaces: impl Iterator<Item = (u32, &'a str, &'a str)>,
130
- ) -> Result<()> {
131
- let mut socket = Socket::open_protocol(libc::NETLINK_ROUTE)?;
132
- for (index, name, kind) in interfaces {
133
- let sequence = socket.sequence()?;
134
- let mut request = Vec::new();
135
- request.extend(32_u32.to_ne_bytes());
136
- request.extend(18_u16.to_ne_bytes());
137
- request.extend(1_u16.to_ne_bytes());
138
- request.extend(sequence.to_ne_bytes());
139
- request.extend(socket.port.to_ne_bytes());
140
- request.extend([0; 4]);
141
- request.extend(index.to_ne_bytes());
142
- request.extend([0; 8]);
143
- socket.send(&request)?;
144
- let until = Instant::now() + Duration::from_secs(3);
145
- let response = 'read: loop {
146
- for message in socket.receive(until)? {
147
- if message.sequence != sequence {
148
- continue;
149
- }
150
- if message.kind == 2 {
151
- let error = i32::from_ne_bytes(
152
- message
153
- .payload
154
- .get(..4)
155
- .ok_or(Error::Protocol)?
156
- .try_into()
157
- .unwrap(),
158
- );
159
- if error != 0 {
160
- return Err(Error::kernel(error));
161
- }
162
- } else if message.kind == 16 && message.payload.len() >= 16 {
163
- if u32::from_ne_bytes(message.payload[4..8].try_into().unwrap()) != index {
164
- return Err(Error::Conflict);
165
- }
166
- break 'read attrs(&message.payload[16..])?;
167
- } else {
168
- return Err(Error::Protocol);
169
- }
170
- }
171
- };
172
- if text(&response, 3)? != name {
173
- return Err(Error::Conflict);
174
- }
175
- if response.iter().any(|attr| attr.id() == 10)
176
- && one(&response, 10)?.value != 0_u32.to_ne_bytes()
177
- {
178
- return Err(Error::Conflict);
179
- }
180
- let info = attrs(&one(&response, 18)?.value)?;
181
- if text(&info, 1)? != kind {
182
- return Err(Error::Conflict);
183
- }
184
- if kind == "tun" && one(&attrs(&one(&info, 2)?.value)?, 3)?.value != [1] {
185
- return Err(Error::Conflict);
186
- }
187
- }
188
- Ok(())
189
- }
126
+ #[path = "wire.links.rs"]
127
+ mod links;
128
+ pub use links::{verify_bound_interfaces, verify_interfaces, BoundInterface};
190
129
  impl Socket {
191
130
  pub fn open() -> Result<Self> {
192
131
  Self::open_protocol(libc::NETLINK_NETFILTER)
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@push.rocks/smartnftables',
6
- version: '1.3.1',
6
+ version: '1.5.0',
7
7
  description: 'A TypeScript module for managing nftables rules including NAT, firewall, and rate limiting with a high-level API.'
8
8
  }