@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,682 @@
1
+ use crate::{policy, Error, Result};
2
+ use serde::{Deserialize, Serialize};
3
+ use std::collections::BTreeSet;
4
+ use std::net::Ipv4Addr;
5
+
6
+ #[path = "egress.compile.rs"]
7
+ mod compile;
8
+
9
+ #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
10
+ #[serde(rename_all = "camelCase", deny_unknown_fields)]
11
+ pub struct LocalLink {
12
+ pub interface_index: u32,
13
+ pub interface_name: String,
14
+ pub interface_kind: String,
15
+ #[serde(deserialize_with = "policy::required_nullable")]
16
+ pub mac_address: Option<String>,
17
+ pub interface_link_index: u32,
18
+ pub required_ipv4_addresses: Vec<String>,
19
+ }
20
+ #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
21
+ #[serde(rename_all = "camelCase", deny_unknown_fields)]
22
+ pub struct PlatformEndpoint {
23
+ pub id: String,
24
+ pub address: String,
25
+ pub protocol: String,
26
+ pub port: u16,
27
+ }
28
+ #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
29
+ #[serde(rename_all = "camelCase", deny_unknown_fields)]
30
+ pub struct Protection {
31
+ pub authority_digest: String,
32
+ pub prefixes: Vec<String>,
33
+ pub platform_endpoints: Vec<PlatformEndpoint>,
34
+ }
35
+ #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord)]
36
+ #[serde(rename_all = "camelCase", deny_unknown_fields)]
37
+ pub struct PortRange {
38
+ pub protocol: String,
39
+ pub first: u16,
40
+ pub last: u16,
41
+ }
42
+ #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
43
+ #[serde(rename_all = "camelCase", deny_unknown_fields)]
44
+ pub struct Lease {
45
+ pub id: String,
46
+ pub generation: u64,
47
+ pub digest: String,
48
+ }
49
+ #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
50
+ #[serde(rename_all = "camelCase", deny_unknown_fields)]
51
+ pub struct Allocation {
52
+ pub lease: Lease,
53
+ pub transit_source_address: String,
54
+ pub source_port_ranges: Vec<PortRange>,
55
+ }
56
+ #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord)]
57
+ #[serde(tag = "kind", rename_all = "camelCase", deny_unknown_fields)]
58
+ pub enum Destination {
59
+ Public {
60
+ prefix: String,
61
+ },
62
+ Platform {
63
+ #[serde(rename = "endpointId")]
64
+ endpoint_id: String,
65
+ },
66
+ }
67
+ #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord)]
68
+ #[serde(rename_all = "camelCase", deny_unknown_fields)]
69
+ pub struct SelectedPortRange {
70
+ pub first: u16,
71
+ pub last: u16,
72
+ }
73
+ #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord)]
74
+ #[serde(rename_all = "camelCase", deny_unknown_fields)]
75
+ pub struct Grant {
76
+ #[serde(deserialize_with = "policy::required_nullable")]
77
+ pub source_endpoint: Option<String>,
78
+ pub source_prefix: String,
79
+ pub destination: Destination,
80
+ pub protocol: String,
81
+ #[serde(deserialize_with = "policy::required_nullable")]
82
+ pub source_port: Option<u16>,
83
+ #[serde(deserialize_with = "policy::required_nullable")]
84
+ pub destination_port: Option<u16>,
85
+ pub source_port_range: SelectedPortRange,
86
+ }
87
+ #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
88
+ #[serde(rename_all = "camelCase", deny_unknown_fields)]
89
+ pub struct Generation {
90
+ pub lease: Lease,
91
+ pub transit_source_address: String,
92
+ pub source_port_ranges: Vec<PortRange>,
93
+ pub conntrack_zone: u16,
94
+ pub conntrack_label: String,
95
+ pub grants: Vec<Grant>,
96
+ }
97
+ #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
98
+ #[serde(rename_all = "camelCase", deny_unknown_fields)]
99
+ pub struct RouterScope {
100
+ pub endpoints: Vec<policy::Endpoint>,
101
+ pub rules: Vec<policy::Rule>,
102
+ pub links: Vec<LocalLink>,
103
+ pub handoff: LocalLink,
104
+ pub protection: Protection,
105
+ pub generations: Vec<Generation>,
106
+ }
107
+ #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
108
+ #[serde(rename_all = "camelCase", deny_unknown_fields)]
109
+ pub struct HostHandoff {
110
+ pub link: LocalLink,
111
+ pub allocations: Vec<Allocation>,
112
+ }
113
+ #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
114
+ #[serde(rename_all = "camelCase", deny_unknown_fields)]
115
+ pub struct HostScope {
116
+ pub protection: Protection,
117
+ pub handoffs: Vec<HostHandoff>,
118
+ pub uplink: LocalLink,
119
+ pub snat_address: String,
120
+ }
121
+ #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
122
+ #[serde(tag = "kind", rename_all = "camelCase", deny_unknown_fields)]
123
+ pub enum Scope {
124
+ RouterEgress(RouterScope),
125
+ HostTransit(HostScope),
126
+ }
127
+ #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
128
+ #[serde(rename_all = "camelCase", deny_unknown_fields)]
129
+ pub struct Policy {
130
+ pub schema_version: u8,
131
+ pub revision: u64,
132
+ pub scope: Scope,
133
+ }
134
+
135
+ #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
136
+ #[serde(rename_all = "camelCase", deny_unknown_fields)]
137
+ pub struct Prepared {
138
+ pub policy: Policy,
139
+ pub digest: String,
140
+ }
141
+
142
+ fn require(value: bool) -> Result<()> {
143
+ if value {
144
+ Ok(())
145
+ } else {
146
+ Err(Error::Invalid)
147
+ }
148
+ }
149
+ fn identifier(value: &str) -> bool {
150
+ !value.is_empty()
151
+ && value.len() <= 200
152
+ && value.as_bytes()[0].is_ascii_alphanumeric()
153
+ && value
154
+ .bytes()
155
+ .all(|item| item.is_ascii_alphanumeric() || b":._-".contains(&item))
156
+ }
157
+ pub fn digest(value: &str) -> bool {
158
+ value.len() == 71
159
+ && value.starts_with("sha256:")
160
+ && value.as_bytes()[7..]
161
+ .iter()
162
+ .all(|item| item.is_ascii_digit() || (b'a'..=b'f').contains(item))
163
+ }
164
+ fn integer(value: u64) -> bool {
165
+ value > 0 && value <= 9_007_199_254_740_991
166
+ }
167
+ fn protocol(value: &str) -> bool {
168
+ ["tcp", "udp"].contains(&value)
169
+ }
170
+ pub fn address(value: &str) -> Result<u32> {
171
+ let ip: Ipv4Addr = value.parse().map_err(|_| Error::Invalid)?;
172
+ require(ip.to_string() == value)?;
173
+ Ok(u32::from(ip))
174
+ }
175
+ fn unicast(value: &str) -> Result<u32> {
176
+ let ip = address(value)?;
177
+ let first = ip >> 24;
178
+ require(first > 0 && first < 224 && first != 127)?;
179
+ Ok(ip)
180
+ }
181
+ pub fn prefix(value: &str) -> Result<(u32, u32)> {
182
+ let (ip, width) = value.split_once('/').ok_or(Error::Invalid)?;
183
+ let width: u32 = width.parse().map_err(|_| Error::Invalid)?;
184
+ require(width <= 32 && value == format!("{ip}/{width}"))?;
185
+ let base = address(ip)?;
186
+ let mask = if width == 0 {
187
+ 0
188
+ } else {
189
+ u32::MAX << (32 - width)
190
+ };
191
+ require(base & mask == base)?;
192
+ Ok((base, mask))
193
+ }
194
+ pub fn covers(outer: &str, inner: &str) -> Result<bool> {
195
+ let (a, am) = prefix(outer)?;
196
+ let (b, bm) = prefix(inner)?;
197
+ Ok(bm & am == am && b & am == a)
198
+ }
199
+ fn overlaps(left: &str, right: &str) -> Result<bool> {
200
+ Ok(covers(left, right)? || covers(right, left)?)
201
+ }
202
+ fn covers_address(prefixes: &[String], ip: &str) -> Result<bool> {
203
+ for item in prefixes {
204
+ if covers(item, &format!("{ip}/32"))? {
205
+ return Ok(true);
206
+ }
207
+ }
208
+ Ok(false)
209
+ }
210
+ fn normalize_link(link: &mut LocalLink) -> Result<()> {
211
+ require(
212
+ link.interface_index > 0
213
+ && link.interface_index <= i32::MAX as u32
214
+ && link.interface_link_index > 0
215
+ && link.interface_link_index <= i32::MAX as u32
216
+ && policy::identifier(&link.interface_name)
217
+ && link.interface_name.len() <= 15
218
+ && link.interface_name != "lo"
219
+ && ["veth", "tun", "ethernet"].contains(&link.interface_kind.as_str())
220
+ && link.required_ipv4_addresses.len() <= 32,
221
+ )?;
222
+ if link.interface_kind == "tun" {
223
+ require(link.mac_address.is_none())?;
224
+ } else {
225
+ let mac = link.mac_address.as_ref().ok_or(Error::Invalid)?;
226
+ let parts: Vec<_> = mac.split(':').collect();
227
+ require(
228
+ parts.len() == 6
229
+ && parts.iter().all(|part| {
230
+ part.len() == 2
231
+ && part
232
+ .bytes()
233
+ .all(|item| item.is_ascii_digit() || (b'a'..=b'f').contains(&item))
234
+ }),
235
+ )?;
236
+ let bytes: Vec<u8> = parts
237
+ .iter()
238
+ .map(|part| u8::from_str_radix(part, 16).unwrap())
239
+ .collect();
240
+ require(bytes[0] & 1 == 0 && bytes.iter().any(|item| *item != 0))?;
241
+ }
242
+ link.required_ipv4_addresses.sort();
243
+ for (index, ip) in link.required_ipv4_addresses.iter().enumerate() {
244
+ unicast(ip)?;
245
+ require(index == 0 || *ip != link.required_ipv4_addresses[index - 1])?;
246
+ }
247
+ Ok(())
248
+ }
249
+ fn unique_links<'a>(links: impl Iterator<Item = &'a LocalLink>) -> Result<()> {
250
+ let mut indices = BTreeSet::new();
251
+ let mut names = BTreeSet::new();
252
+ for link in links {
253
+ require(indices.insert(link.interface_index) && names.insert(&link.interface_name))?;
254
+ }
255
+ Ok(())
256
+ }
257
+ fn normalize_protection(value: &mut Protection) -> Result<()> {
258
+ require(
259
+ digest(&value.authority_digest)
260
+ && !value.prefixes.is_empty()
261
+ && value.prefixes.len() <= 128
262
+ && value.platform_endpoints.len() <= 96,
263
+ )?;
264
+ value.prefixes.sort();
265
+ for (index, item) in value.prefixes.iter().enumerate() {
266
+ prefix(item)?;
267
+ for previous in &value.prefixes[..index] {
268
+ require(!overlaps(item, previous)?)?;
269
+ }
270
+ }
271
+ value.platform_endpoints.sort_by(|a, b| a.id.cmp(&b.id));
272
+ let mut tuples = BTreeSet::new();
273
+ for (index, endpoint) in value.platform_endpoints.iter().enumerate() {
274
+ require(
275
+ identifier(&endpoint.id)
276
+ && protocol(&endpoint.protocol)
277
+ && endpoint.port > 0
278
+ && (index == 0 || endpoint.id != value.platform_endpoints[index - 1].id),
279
+ )?;
280
+ unicast(&endpoint.address)?;
281
+ require(
282
+ covers_address(&value.prefixes, &endpoint.address)?
283
+ && tuples.insert((&endpoint.address, &endpoint.protocol, endpoint.port)),
284
+ )?;
285
+ }
286
+ Ok(())
287
+ }
288
+ fn normalize_allocation(
289
+ lease: &Lease,
290
+ source: &str,
291
+ ranges: &mut [PortRange],
292
+ protection: &Protection,
293
+ ) -> Result<()> {
294
+ require(
295
+ identifier(&lease.id)
296
+ && integer(lease.generation)
297
+ && digest(&lease.digest)
298
+ && !ranges.is_empty()
299
+ && ranges.len() <= 16,
300
+ )?;
301
+ unicast(source)?;
302
+ require(covers_address(&protection.prefixes, source)?)?;
303
+ require(
304
+ protection
305
+ .platform_endpoints
306
+ .iter()
307
+ .all(|endpoint| endpoint.address != source),
308
+ )?;
309
+ ranges.sort();
310
+ for (index, range) in ranges.iter().enumerate() {
311
+ require(protocol(&range.protocol) && range.first > 0 && range.first <= range.last)?;
312
+ if index > 0 {
313
+ let previous = &ranges[index - 1];
314
+ require(previous.protocol != range.protocol || previous.last < range.first)?;
315
+ }
316
+ }
317
+ Ok(())
318
+ }
319
+ fn unique_allocations<'a>(
320
+ items: impl Iterator<Item = (&'a Lease, &'a str, &'a [PortRange])>,
321
+ ) -> Result<()> {
322
+ let mut leases = BTreeSet::new();
323
+ let mut prior: Vec<(&str, &[PortRange])> = Vec::new();
324
+ for (lease, source, ranges) in items {
325
+ require(leases.insert((&lease.id, lease.generation)))?;
326
+ for (other_source, other_ranges) in &prior {
327
+ if *other_source == source {
328
+ for left in ranges {
329
+ for right in *other_ranges {
330
+ require(
331
+ left.protocol != right.protocol
332
+ || left.last < right.first
333
+ || right.last < left.first,
334
+ )?;
335
+ }
336
+ }
337
+ }
338
+ }
339
+ prior.push((source, ranges));
340
+ }
341
+ Ok(())
342
+ }
343
+ pub fn destination(grant: &Grant, protection: &Protection) -> Result<String> {
344
+ match &grant.destination {
345
+ Destination::Public { prefix: value } => {
346
+ prefix(value)?;
347
+ Ok(value.clone())
348
+ }
349
+ Destination::Platform { endpoint_id } => {
350
+ let endpoint = protection
351
+ .platform_endpoints
352
+ .iter()
353
+ .find(|item| item.id == *endpoint_id)
354
+ .ok_or(Error::Invalid)?;
355
+ require(
356
+ grant.protocol == endpoint.protocol
357
+ && grant.destination_port == Some(endpoint.port),
358
+ )?;
359
+ Ok(format!("{}/32", endpoint.address))
360
+ }
361
+ }
362
+ }
363
+ fn normalize_router(value: &mut RouterScope, revision: u64) -> Result<()> {
364
+ require(value.links.len() <= 32 && value.generations.len() <= 32)?;
365
+ let private = policy::Policy {
366
+ schema_version: 1,
367
+ revision,
368
+ endpoints: value.endpoints.clone(),
369
+ rules: value.rules.clone(),
370
+ }
371
+ .prepare()?;
372
+ value.endpoints = private.policy.endpoints;
373
+ value.rules = private.policy.rules;
374
+ normalize_protection(&mut value.protection)?;
375
+ normalize_link(&mut value.handoff)?;
376
+ require(
377
+ value.handoff.interface_kind == "veth"
378
+ && !value.handoff.required_ipv4_addresses.is_empty()
379
+ && value.links.len() == value.endpoints.len(),
380
+ )?;
381
+ for link in &mut value.links {
382
+ normalize_link(link)?;
383
+ }
384
+ value.links.sort_by_key(|link| link.interface_index);
385
+ unique_links(value.links.iter().chain(std::iter::once(&value.handoff)))?;
386
+ for endpoint in &value.endpoints {
387
+ require(value.links.iter().any(|link| {
388
+ link.interface_index == endpoint.interface_index
389
+ && link.interface_name == endpoint.interface_name
390
+ && link.interface_kind == endpoint.interface_kind
391
+ }))?;
392
+ for source in &endpoint.source_prefixes {
393
+ require(
394
+ value
395
+ .protection
396
+ .prefixes
397
+ .iter()
398
+ .any(|protected| covers(protected, source) == Ok(true)),
399
+ )?;
400
+ for exception in &value.protection.platform_endpoints {
401
+ require(!covers(source, &format!("{}/32", exception.address))?)?;
402
+ }
403
+ }
404
+ }
405
+ for link in value.links.iter().chain(std::iter::once(&value.handoff)) {
406
+ for ip in &link.required_ipv4_addresses {
407
+ require(
408
+ covers_address(&value.protection.prefixes, ip)?
409
+ && value
410
+ .protection
411
+ .platform_endpoints
412
+ .iter()
413
+ .all(|endpoint| endpoint.address != *ip),
414
+ )?;
415
+ }
416
+ }
417
+ for rule in &value.rules {
418
+ for item in [&rule.source_prefix, &rule.destination_prefix] {
419
+ require(
420
+ value
421
+ .protection
422
+ .prefixes
423
+ .iter()
424
+ .any(|protected| covers(protected, item) == Ok(true)),
425
+ )?;
426
+ }
427
+ }
428
+ let mut zones = BTreeSet::new();
429
+ let mut labels = BTreeSet::new();
430
+ let mut classifiers: Vec<(&Grant, String)> = Vec::new();
431
+ let mut count = 0;
432
+ value
433
+ .generations
434
+ .sort_by(|a, b| (&a.lease.id, a.lease.generation).cmp(&(&b.lease.id, b.lease.generation)));
435
+ for generation in &mut value.generations {
436
+ normalize_allocation(
437
+ &generation.lease,
438
+ &generation.transit_source_address,
439
+ &mut generation.source_port_ranges,
440
+ &value.protection,
441
+ )?;
442
+ require(
443
+ value
444
+ .handoff
445
+ .required_ipv4_addresses
446
+ .contains(&generation.transit_source_address)
447
+ && generation.conntrack_zone > 0
448
+ && zones.insert(generation.conntrack_zone)
449
+ && generation.conntrack_label.len() == 32
450
+ && generation
451
+ .conntrack_label
452
+ .bytes()
453
+ .all(|item| item.is_ascii_digit() || (b'a'..=b'f').contains(&item))
454
+ && generation.conntrack_label.bytes().any(|item| item != b'0')
455
+ && labels.insert(generation.conntrack_label.clone()),
456
+ )?;
457
+ count += generation.grants.len();
458
+ require(count <= 128)?;
459
+ generation.grants.sort();
460
+ for grant in &generation.grants {
461
+ require(
462
+ protocol(&grant.protocol)
463
+ && grant.source_port != Some(0)
464
+ && grant.destination_port != Some(0),
465
+ )?;
466
+ prefix(&grant.source_prefix)?;
467
+ if let Some(endpoint_id) = &grant.source_endpoint {
468
+ let endpoint = value
469
+ .endpoints
470
+ .iter()
471
+ .find(|item| item.id == *endpoint_id)
472
+ .ok_or(Error::Invalid)?;
473
+ require(
474
+ endpoint.interface_kind == "veth"
475
+ && endpoint
476
+ .source_prefixes
477
+ .iter()
478
+ .any(|source| covers(source, &grant.source_prefix) == Ok(true)),
479
+ )?;
480
+ } else {
481
+ let (ip, mask) = prefix(&grant.source_prefix)?;
482
+ require(
483
+ mask == u32::MAX
484
+ && value
485
+ .links
486
+ .iter()
487
+ .chain(std::iter::once(&value.handoff))
488
+ .any(|link| {
489
+ link.required_ipv4_addresses
490
+ .contains(&Ipv4Addr::from(ip).to_string())
491
+ }),
492
+ )?;
493
+ }
494
+ require(generation.source_port_ranges.iter().any(|range| {
495
+ range.protocol == grant.protocol
496
+ && range.first == grant.source_port_range.first
497
+ && range.last == grant.source_port_range.last
498
+ }))?;
499
+ let target = destination(grant, &value.protection)?;
500
+ if let Destination::Public { .. } = &grant.destination {
501
+ require(
502
+ !value
503
+ .protection
504
+ .prefixes
505
+ .iter()
506
+ .any(|protected| covers(protected, &target) == Ok(true)),
507
+ )?;
508
+ }
509
+ for (previous, previous_target) in &classifiers {
510
+ let same_kind = std::mem::discriminant(&previous.destination)
511
+ == std::mem::discriminant(&grant.destination);
512
+ let ports_overlap =
513
+ |a: Option<u16>, b: Option<u16>| a.is_none() || b.is_none() || a == b;
514
+ require(
515
+ !(same_kind
516
+ && previous.source_endpoint == grant.source_endpoint
517
+ && previous.protocol == grant.protocol
518
+ && ports_overlap(previous.source_port, grant.source_port)
519
+ && ports_overlap(previous.destination_port, grant.destination_port)
520
+ && overlaps(&previous.source_prefix, &grant.source_prefix)?
521
+ && overlaps(previous_target, &target)?),
522
+ )?;
523
+ }
524
+ classifiers.push((grant, target));
525
+ }
526
+ }
527
+ unique_allocations(value.generations.iter().map(|item| {
528
+ (
529
+ &item.lease,
530
+ item.transit_source_address.as_str(),
531
+ item.source_port_ranges.as_slice(),
532
+ )
533
+ }))
534
+ }
535
+ fn normalize_host(value: &mut HostScope) -> Result<()> {
536
+ normalize_protection(&mut value.protection)?;
537
+ normalize_link(&mut value.uplink)?;
538
+ require(
539
+ value.handoffs.len() <= 32
540
+ && value.uplink.interface_kind != "tun"
541
+ && value
542
+ .uplink
543
+ .required_ipv4_addresses
544
+ .contains(&value.snat_address),
545
+ )?;
546
+ unicast(&value.snat_address)?;
547
+ value
548
+ .handoffs
549
+ .sort_by_key(|handoff| handoff.link.interface_index);
550
+ let mut count = 0;
551
+ let mut addresses = std::collections::BTreeMap::new();
552
+ let local_addresses: BTreeSet<_> = value
553
+ .handoffs
554
+ .iter()
555
+ .flat_map(|item| item.link.required_ipv4_addresses.iter().cloned())
556
+ .chain(value.uplink.required_ipv4_addresses.iter().cloned())
557
+ .collect();
558
+ for ip in &local_addresses {
559
+ require(
560
+ covers_address(&value.protection.prefixes, ip)?
561
+ && value
562
+ .protection
563
+ .platform_endpoints
564
+ .iter()
565
+ .all(|endpoint| endpoint.address != *ip),
566
+ )?;
567
+ }
568
+ for handoff in &mut value.handoffs {
569
+ normalize_link(&mut handoff.link)?;
570
+ require(
571
+ handoff.link.interface_kind == "veth"
572
+ && !handoff.link.required_ipv4_addresses.is_empty(),
573
+ )?;
574
+ for ip in &handoff.link.required_ipv4_addresses {
575
+ require(covers_address(&value.protection.prefixes, ip)?)?;
576
+ }
577
+ handoff.allocations.sort_by(|a, b| {
578
+ (&a.lease.id, a.lease.generation).cmp(&(&b.lease.id, b.lease.generation))
579
+ });
580
+ count += handoff.allocations.len();
581
+ require(count <= 32)?;
582
+ for allocation in &mut handoff.allocations {
583
+ normalize_allocation(
584
+ &allocation.lease,
585
+ &allocation.transit_source_address,
586
+ &mut allocation.source_port_ranges,
587
+ &value.protection,
588
+ )?;
589
+ require(
590
+ allocation.transit_source_address != value.snat_address
591
+ && !local_addresses.contains(&allocation.transit_source_address),
592
+ )?;
593
+ if let Some(index) = addresses.insert(
594
+ allocation.transit_source_address.clone(),
595
+ handoff.link.interface_index,
596
+ ) {
597
+ require(index == handoff.link.interface_index)?;
598
+ }
599
+ }
600
+ }
601
+ unique_links(
602
+ value
603
+ .handoffs
604
+ .iter()
605
+ .map(|item| &item.link)
606
+ .chain(std::iter::once(&value.uplink)),
607
+ )?;
608
+ unique_allocations(
609
+ value
610
+ .handoffs
611
+ .iter()
612
+ .flat_map(|item| item.allocations.iter())
613
+ .map(|item| {
614
+ (
615
+ &item.lease,
616
+ item.transit_source_address.as_str(),
617
+ item.source_port_ranges.as_slice(),
618
+ )
619
+ }),
620
+ )
621
+ }
622
+
623
+ #[cfg(test)]
624
+ #[path = "egress_tests.rs"]
625
+ pub(crate) mod tests;
626
+ impl Policy {
627
+ pub fn prepare(self) -> Result<Prepared> {
628
+ use sha2::{Digest, Sha256};
629
+ let policy = self.normalize()?;
630
+ let mut hash = Sha256::new();
631
+ hash.update(b"smartnftables/managed-policy-v2\0");
632
+ hash.update(serde_json::to_vec(&policy).map_err(|_| Error::Invalid)?);
633
+ let result = Prepared {
634
+ policy,
635
+ digest: format!("sha256:{:x}", hash.finalize()),
636
+ };
637
+ result.program(&format!("snft_{}", "x".repeat(59)))?;
638
+ Ok(result)
639
+ }
640
+ pub fn normalize(mut self) -> Result<Self> {
641
+ require(self.schema_version == 2 && integer(self.revision))?;
642
+ match &mut self.scope {
643
+ Scope::RouterEgress(scope) => normalize_router(scope, self.revision)?,
644
+ Scope::HostTransit(scope) => normalize_host(scope)?,
645
+ }
646
+ Ok(self)
647
+ }
648
+ }
649
+
650
+ impl Prepared {
651
+ pub fn validate(&self) -> Result<()> {
652
+ require(self.policy.clone().prepare()? == *self)
653
+ }
654
+ pub fn program(&self, table: &str) -> Result<Vec<(u16, Vec<crate::wire::Attr>)>> {
655
+ compile::compile(self, table)
656
+ }
657
+ pub fn validate_interfaces(&self) -> Result<()> {
658
+ let links: Vec<&LocalLink> = match &self.policy.scope {
659
+ Scope::RouterEgress(scope) => scope
660
+ .links
661
+ .iter()
662
+ .chain(std::iter::once(&scope.handoff))
663
+ .collect(),
664
+ Scope::HostTransit(scope) => scope
665
+ .handoffs
666
+ .iter()
667
+ .map(|item| &item.link)
668
+ .chain(std::iter::once(&scope.uplink))
669
+ .collect(),
670
+ };
671
+ crate::wire::verify_bound_interfaces(links.into_iter().map(|link| {
672
+ crate::wire::BoundInterface {
673
+ index: link.interface_index,
674
+ name: &link.interface_name,
675
+ kind: &link.interface_kind,
676
+ mac: link.mac_address.as_deref(),
677
+ link_index: link.interface_link_index,
678
+ addresses: &link.required_ipv4_addresses,
679
+ }
680
+ }))
681
+ }
682
+ }