@push.rocks/smartnftables 1.4.0 → 1.6.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 (53) hide show
  1. package/changelog.md +26 -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.manageddockerforwarding.d.ts +17 -0
  8. package/dist_ts/classes.manageddockerforwarding.js +128 -0
  9. package/dist_ts/classes.managednftables.d.ts +9 -7
  10. package/dist_ts/classes.managednftables.js +2 -2
  11. package/dist_ts/index.d.ts +3 -0
  12. package/dist_ts/index.js +3 -1
  13. package/dist_ts/managed.docker.types.d.ts +78 -0
  14. package/dist_ts/managed.docker.types.js +2 -0
  15. package/dist_ts/managed.egress.types.d.ts +91 -0
  16. package/dist_ts/managed.egress.types.js +2 -0
  17. package/dist_ts/managed.types.d.ts +20 -18
  18. package/package.json +2 -2
  19. package/readme.md +143 -6
  20. package/rust/src/docker.frontend.rs +203 -0
  21. package/rust/src/docker.graph.rs +129 -0
  22. package/rust/src/docker.policy.rs +140 -0
  23. package/rust/src/docker.rs +191 -0
  24. package/rust/src/docker_process_tests.rs +29 -0
  25. package/rust/src/docker_tests.rs +148 -0
  26. package/rust/src/egress.compile.rs +288 -0
  27. package/rust/src/egress.host.rs +103 -0
  28. package/rust/src/egress.router.rs +231 -0
  29. package/rust/src/egress.rs +682 -0
  30. package/rust/src/egress_tests.rs +332 -0
  31. package/rust/src/main.rs +13 -6
  32. package/rust/src/managed.rs +105 -0
  33. package/rust/src/owner.rs +115 -7
  34. package/rust/src/owner_coexistence_tests.rs +77 -0
  35. package/rust/src/owner_egress_identity_tests.rs +155 -0
  36. package/rust/src/owner_egress_tests.rs +153 -0
  37. package/rust/src/owner_egress_traffic_tests.rs +470 -0
  38. package/rust/src/owner_host_traffic_tests.rs +305 -0
  39. package/rust/src/owner_identity_tests.rs +169 -0
  40. package/rust/src/owner_link_tests.rs +119 -0
  41. package/rust/src/owner_packet_fixture.rs +200 -0
  42. package/rust/src/owner_tests.rs +33 -7
  43. package/rust/src/policy.rs +120 -81
  44. package/rust/src/tests.rs +22 -0
  45. package/rust/src/wire.links.rs +279 -0
  46. package/rust/src/wire.rs +16 -66
  47. package/ts/00_commitinfo_data.ts +1 -1
  48. package/ts/classes.manageddockerforwarding.ts +113 -0
  49. package/ts/classes.managednftables.ts +13 -13
  50. package/ts/index.ts +3 -0
  51. package/ts/managed.docker.types.ts +49 -0
  52. package/ts/managed.egress.types.ts +85 -0
  53. package/ts/managed.types.ts +20 -16
@@ -0,0 +1,332 @@
1
+ use super::{Policy, Scope};
2
+ use serde_json::{json, Value};
3
+
4
+ fn link(index: u32, name: &str, kind: &str, address: &str) -> Value {
5
+ json!({"interfaceIndex":index,"interfaceName":name,"interfaceKind":kind,
6
+ "macAddress":if kind == "tun" { Value::Null } else { json!(format!("02:00:00:00:00:{index:02x}")) },
7
+ "interfaceLinkIndex":index+100,"requiredIpv4Addresses":[address]})
8
+ }
9
+ fn allocation() -> Value {
10
+ json!({"lease":{"id":"lease-a","generation":1,"digest":format!("sha256:{}","a".repeat(64))},
11
+ "transitSourceAddress":"10.240.0.2",
12
+ "sourcePortRanges":[{"protocol":"tcp","first":10000,"last":10999},{"protocol":"udp","first":10000,"last":10999}]})
13
+ }
14
+ fn protection() -> Value {
15
+ json!({"authorityDigest":format!("sha256:{}","b".repeat(64)),
16
+ "prefixes":["10.0.0.0/8","192.0.2.0/24","203.0.113.10/32"],
17
+ "platformEndpoints":[{"id":"dns","address":"192.0.2.1","protocol":"udp","port":53},
18
+ {"id":"relay","address":"203.0.113.10","protocol":"tcp","port":443}]})
19
+ }
20
+ pub(crate) fn router() -> Value {
21
+ let mut generation = allocation();
22
+ generation["conntrackZone"] = json!(1);
23
+ generation["conntrackLabel"] = json!("a".repeat(32));
24
+ generation["grants"] = json!([
25
+ {"sourceEndpoint":"work","sourcePrefix":"10.241.0.2/32","destination":{"kind":"public","prefix":"0.0.0.0/0"},
26
+ "protocol":"tcp","sourcePort":null,"destinationPort":443,"sourcePortRange":{"first":10000,"last":10999}},
27
+ {"sourceEndpoint":null,"sourcePrefix":"10.240.0.2/32","destination":{"kind":"platform","endpointId":"dns"},
28
+ "protocol":"udp","sourcePort":null,"destinationPort":53,"sourcePortRange":{"first":10000,"last":10999}},
29
+ {"sourceEndpoint":null,"sourcePrefix":"10.240.0.2/32","destination":{"kind":"platform","endpointId":"relay"},
30
+ "protocol":"tcp","sourcePort":null,"destinationPort":443,"sourcePortRange":{"first":10000,"last":10999}}
31
+ ]);
32
+ json!({"schemaVersion":2,"revision":1,"scope":{"kind":"routerEgress",
33
+ "endpoints":[
34
+ {"id":"work","interfaceIndex":2,"interfaceName":"work","interfaceKind":"veth","sourcePrefixes":["10.241.0.2/32"]},
35
+ {"id":"vpn","interfaceIndex":3,"interfaceName":"vpn","interfaceKind":"tun","sourcePrefixes":["10.242.0.0/24"]}],
36
+ "rules":[
37
+ {"sourceEndpoint":"work","destinationEndpoint":"vpn","sourcePrefix":"10.241.0.2/32","destinationPrefix":"10.242.0.2/32","protocol":"udp","sourcePort":null,"destinationPort":53},
38
+ {"sourceEndpoint":"work","destinationEndpoint":null,"sourcePrefix":"10.241.0.2/32","destinationPrefix":"10.241.0.1/32","protocol":"udp","sourcePort":null,"destinationPort":53},
39
+ {"sourceEndpoint":null,"destinationEndpoint":"work","sourcePrefix":"10.241.0.1/32","destinationPrefix":"10.241.0.2/32","protocol":"udp","sourcePort":53,"destinationPort":null}],
40
+ "links":[link(2,"work","veth","10.241.0.1"),link(3,"vpn","tun","10.242.0.1")],
41
+ "handoff":link(4,"handoff","veth","10.240.0.2"),"protection":protection(),"generations":[generation]}})
42
+ }
43
+ pub(crate) fn host() -> Value {
44
+ json!({"schemaVersion":2,"revision":1,"scope":{"kind":"hostTransit","protection":protection(),
45
+ "handoffs":[{"link":link(4,"handoff","veth","10.240.0.1"),"allocations":[allocation()]}],
46
+ "uplink":link(2,"ens18","ethernet","192.0.2.2"),"snatAddress":"192.0.2.2"}})
47
+ }
48
+ fn normalized(value: Value) -> super::Result<Policy> {
49
+ serde_json::from_value::<Policy>(value)
50
+ .map_err(|_| crate::Error::Invalid)?
51
+ .normalize()
52
+ }
53
+ fn changed(mut value: Value, path: &str, replacement: Value) -> Value {
54
+ *value.pointer_mut(path).unwrap() = replacement;
55
+ value
56
+ }
57
+
58
+ #[test]
59
+ fn combined_router_preserves_private_tun_dns_and_explicit_local_egress() {
60
+ let expected = normalized(router()).unwrap();
61
+ let mut reordered = router();
62
+ for path in [
63
+ "/scope/endpoints",
64
+ "/scope/rules",
65
+ "/scope/links",
66
+ "/scope/protection/prefixes",
67
+ "/scope/protection/platformEndpoints",
68
+ "/scope/generations/0/sourcePortRanges",
69
+ "/scope/generations/0/grants",
70
+ ] {
71
+ reordered
72
+ .pointer_mut(path)
73
+ .unwrap()
74
+ .as_array_mut()
75
+ .unwrap()
76
+ .reverse();
77
+ }
78
+ assert_eq!(normalized(reordered).unwrap(), expected);
79
+ let denied = normalized(changed(router(), "/scope/generations", json!([]))).unwrap();
80
+ let Scope::RouterEgress(denied) = denied.scope else {
81
+ panic!()
82
+ };
83
+ assert_eq!(denied.endpoints.len(), 2);
84
+ assert_eq!(denied.rules.len(), 3);
85
+ assert!(denied.generations.is_empty());
86
+ assert!(normalized(host()).is_ok());
87
+ }
88
+
89
+ #[test]
90
+ fn v2_rejects_unknown_fields_missing_nulls_and_wrong_scope_shapes() {
91
+ let mut unknown = router();
92
+ unknown["commands"] = json!([]);
93
+ let mut no_null = router();
94
+ no_null["scope"]["generations"][0]["grants"][0]
95
+ .as_object_mut()
96
+ .unwrap()
97
+ .remove("sourcePort");
98
+ let mut mixed = host();
99
+ mixed["scope"]["generations"] = json!([]);
100
+ for invalid in [
101
+ unknown,
102
+ no_null,
103
+ mixed,
104
+ changed(router(), "/schemaVersion", json!(1)),
105
+ changed(router(), "/revision", json!(0)),
106
+ changed(router(), "/revision", json!(9007199254740992_u64)),
107
+ changed(router(), "/scope/kind", json!("egress")),
108
+ ] {
109
+ assert!(normalized(invalid).is_err());
110
+ }
111
+ let duplicate = serde_json::to_string(&router()).unwrap().replace(
112
+ "\"schemaVersion\":2",
113
+ "\"schemaVersion\":2,\"schemaVersion\":2",
114
+ );
115
+ assert!(serde_json::from_str::<Policy>(&duplicate).is_err());
116
+ }
117
+
118
+ #[test]
119
+ fn v2_binds_exact_local_links_addresses_and_complete_protection() {
120
+ for (path, replacement) in [
121
+ ("/scope/handoff/interfaceKind", json!("ethernet")),
122
+ ("/scope/handoff/interfaceIndex", json!(2)),
123
+ ("/scope/handoff/macAddress", json!("03:00:00:00:00:04")),
124
+ ("/scope/handoff/macAddress", json!("00:00:00:00:00:00")),
125
+ ("/scope/handoff/interfaceLinkIndex", json!(0)),
126
+ ("/scope/links/0/interfaceName", json!("foreign")),
127
+ ("/scope/links/1/macAddress", json!("02:00:00:00:00:03")),
128
+ (
129
+ "/scope/links/0/requiredIpv4Addresses",
130
+ json!(["10.241.0.1", "10.241.0.1"]),
131
+ ),
132
+ ("/scope/protection/prefixes", json!(["10.0.0.0/8"])),
133
+ (
134
+ "/scope/protection/prefixes",
135
+ json!([
136
+ "10.0.0.0/8",
137
+ "10.240.0.0/16",
138
+ "192.0.2.0/24",
139
+ "203.0.113.10/32"
140
+ ]),
141
+ ),
142
+ (
143
+ "/scope/protection/platformEndpoints/0/address",
144
+ json!("10.241.0.1"),
145
+ ),
146
+ (
147
+ "/scope/protection/platformEndpoints/0/address",
148
+ json!("10.241.0.2"),
149
+ ),
150
+ ("/scope/protection/authorityDigest", json!("sha256:bad")),
151
+ ] {
152
+ assert!(
153
+ normalized(changed(router(), path, replacement)).is_err(),
154
+ "{path}"
155
+ );
156
+ }
157
+ for (path, replacement) in [
158
+ ("/scope/snatAddress", json!("192.0.2.4")),
159
+ ("/scope/uplink/interfaceKind", json!("tun")),
160
+ (
161
+ "/scope/handoffs/0/allocations/0/transitSourceAddress",
162
+ json!("10.240.0.1"),
163
+ ),
164
+ (
165
+ "/scope/protection/platformEndpoints/0/address",
166
+ json!("192.0.2.2"),
167
+ ),
168
+ ] {
169
+ assert!(
170
+ normalized(changed(host(), path, replacement)).is_err(),
171
+ "{path}"
172
+ );
173
+ }
174
+ }
175
+
176
+ #[test]
177
+ fn v2_rejects_unleased_ports_unbound_sources_and_ambiguous_platform_grants() {
178
+ for (path, replacement) in [
179
+ ("/scope/generations/0/lease/generation", json!(0)),
180
+ ("/scope/generations/0/conntrackZone", json!(0)),
181
+ ("/scope/generations/0/conntrackZone", json!(65536)),
182
+ ("/scope/generations/0/conntrackLabel", json!("0".repeat(32))),
183
+ (
184
+ "/scope/generations/0/transitSourceAddress",
185
+ json!("10.240.0.3"),
186
+ ),
187
+ ("/scope/generations/0/sourcePortRanges/0/first", json!(0)),
188
+ ("/scope/generations/0/sourcePortRanges/0/last", json!(9999)),
189
+ (
190
+ "/scope/generations/0/grants/0/sourcePortRange/last",
191
+ json!(11000),
192
+ ),
193
+ ("/scope/generations/0/grants/0/sourceEndpoint", json!("vpn")),
194
+ (
195
+ "/scope/generations/0/grants/0/sourcePrefix",
196
+ json!("10.241.0.0/24"),
197
+ ),
198
+ ("/scope/generations/0/grants/0/sourcePort", json!(0)),
199
+ (
200
+ "/scope/generations/0/grants/0/destination/prefix",
201
+ json!("10.242.0.0/24"),
202
+ ),
203
+ (
204
+ "/scope/generations/0/grants/1/sourcePrefix",
205
+ json!("10.240.0.0/24"),
206
+ ),
207
+ ("/scope/generations/0/grants/1/destinationPort", Value::Null),
208
+ ("/scope/generations/0/grants/1/protocol", json!("tcp")),
209
+ ] {
210
+ assert!(
211
+ normalized(changed(router(), path, replacement)).is_err(),
212
+ "{path}"
213
+ );
214
+ }
215
+ }
216
+
217
+ #[test]
218
+ fn v2_classification_and_return_ranges_are_disjoint_between_active_generations() {
219
+ let mut value = router();
220
+ value["scope"]["generations"][0]["grants"]
221
+ .as_array_mut()
222
+ .unwrap()
223
+ .truncate(1);
224
+ let mut next = value["scope"]["generations"][0].clone();
225
+ next["lease"]["id"] = json!("lease-b");
226
+ next["conntrackZone"] = json!(2);
227
+ next["conntrackLabel"] = json!("b".repeat(32));
228
+ next["sourcePortRanges"] = json!([{"protocol":"tcp","first":11000,"last":11999}]);
229
+ next["grants"][0]["sourcePortRange"] = json!({"first":11000,"last":11999});
230
+ value["scope"]["generations"]
231
+ .as_array_mut()
232
+ .unwrap()
233
+ .push(next);
234
+ assert!(normalized(value.clone()).is_err());
235
+ value["scope"]["generations"][0]["grants"][0]["sourcePort"] = json!(1000);
236
+ value["scope"]["generations"][1]["grants"][0]["sourcePort"] = json!(2000);
237
+ assert!(normalized(value.clone()).is_ok());
238
+ for (path, replacement) in [
239
+ ("/scope/generations/1/conntrackZone", json!(1)),
240
+ ("/scope/generations/1/conntrackLabel", json!("a".repeat(32))),
241
+ ("/scope/generations/1/lease/id", json!("lease-a")),
242
+ ] {
243
+ assert!(normalized(changed(value.clone(), path, replacement)).is_err());
244
+ }
245
+ value["scope"]["generations"][1]["sourcePortRanges"][0] =
246
+ json!({"protocol":"tcp","first":10500,"last":11500});
247
+ value["scope"]["generations"][1]["grants"][0]["sourcePortRange"] =
248
+ json!({"first":10500,"last":11500});
249
+ assert!(normalized(value).is_err());
250
+ }
251
+
252
+ #[test]
253
+ fn host_never_assigns_the_same_transit_source_to_different_active_handoff_links() {
254
+ let mut value = host();
255
+ let mut other = value["scope"]["handoffs"][0].clone();
256
+ other["link"] = link(5, "handoff-b", "veth", "10.240.0.5");
257
+ other["allocations"][0]["lease"]["id"] = json!("lease-b");
258
+ other["allocations"][0]["sourcePortRanges"] =
259
+ json!([{"protocol":"udp","first":12000,"last":12999}]);
260
+ value["scope"]["handoffs"]
261
+ .as_array_mut()
262
+ .unwrap()
263
+ .push(other);
264
+ assert!(normalized(value.clone()).is_err());
265
+ value["scope"]["handoffs"][1]["allocations"][0]["transitSourceAddress"] = json!("10.240.0.6");
266
+ assert!(normalized(value).is_ok());
267
+ }
268
+
269
+ #[test]
270
+ fn prepared_v2_compiles_both_scopes_with_bounded_and_identifiable_graphs() {
271
+ for fixture in [router(), host()] {
272
+ let prepared = serde_json::from_value::<Policy>(fixture)
273
+ .unwrap()
274
+ .prepare()
275
+ .unwrap();
276
+ prepared.validate().unwrap();
277
+ let program = prepared.program("snft_v2_fixture").unwrap();
278
+ assert!(program.len() <= 768);
279
+ assert!(
280
+ program
281
+ .iter()
282
+ .map(|(_, attributes)| crate::wire::encode_attrs(attributes).len() + 20)
283
+ .sum::<usize>()
284
+ <= 100_000
285
+ );
286
+ assert!(program.iter().all(|(kind, _)| [3, 6].contains(kind)));
287
+ let mut changed = prepared.clone();
288
+ changed.policy.revision += 1;
289
+ assert!(changed.validate().is_err());
290
+ assert_ne!(changed.policy.prepare().unwrap().digest, prepared.digest);
291
+ println!("V2_GRAPH operations={}", program.len());
292
+ }
293
+ }
294
+
295
+ #[test]
296
+ fn v2_compiled_graph_limit_rejects_bounded_but_excessive_cross_products() {
297
+ let mut value = router();
298
+ let mut grants = Vec::new();
299
+ for port in 1000..1032 {
300
+ let mut grant = value["scope"]["generations"][0]["grants"][0].clone();
301
+ grant["destinationPort"] = json!(port);
302
+ grants.push(grant);
303
+ }
304
+ value["scope"]["generations"][0]["grants"] = json!(grants);
305
+ let policy = normalized(value).unwrap();
306
+ assert!(policy.prepare().is_err());
307
+ }
308
+
309
+ #[test]
310
+ fn managed_union_keeps_strict_duplicate_and_unknown_field_rejection() {
311
+ for value in [router(), host()] {
312
+ let text = serde_json::to_string(&value).unwrap();
313
+ assert!(serde_json::from_str::<crate::managed::Policy>(&text).is_ok());
314
+ for invalid in [
315
+ text.replace(
316
+ "\"schemaVersion\":2",
317
+ "\"schemaVersion\":2,\"schemaVersion\":2",
318
+ ),
319
+ text.replace("\"revision\":1", "\"revision\":1,\"revision\":2"),
320
+ text.replace(
321
+ "\"interfaceIndex\":2",
322
+ "\"interfaceIndex\":2,\"interfaceIndex\":2",
323
+ ),
324
+ ] {
325
+ assert!(serde_json::from_str::<crate::managed::Policy>(&invalid).is_err());
326
+ }
327
+ }
328
+ assert!(serde_json::from_str::<crate::managed::Policy>(
329
+ r#"{"schemaVersion":1,"schemaVersion":1,"revision":1,"endpoints":[],"rules":[]}"#
330
+ )
331
+ .is_err());
332
+ }
package/rust/src/main.rs CHANGED
@@ -1,3 +1,6 @@
1
+ mod egress;
2
+ mod docker;
3
+ mod managed;
1
4
  mod namespace;
2
5
  mod owner;
3
6
  mod policy;
@@ -12,6 +15,7 @@ use std::io::{BufRead, Read, Write};
12
15
  pub enum Error {
13
16
  Invalid,
14
17
  Unsupported,
18
+ UnsupportedBackend,
15
19
  Permission,
16
20
  ForeignOwner,
17
21
  Conflict,
@@ -25,6 +29,7 @@ impl Error {
25
29
  match self {
26
30
  Self::Invalid => "INVALID",
27
31
  Self::Unsupported => "UNSUPPORTED_KERNEL",
32
+ Self::UnsupportedBackend => "UNSUPPORTED_BACKEND",
28
33
  Self::Permission => "PERMISSION",
29
34
  Self::ForeignOwner => "FOREIGN_OWNER",
30
35
  Self::Conflict => "CONFLICT",
@@ -59,7 +64,7 @@ fn encode(value: impl serde::Serialize) -> Result<serde_json::Value> {
59
64
  }
60
65
  fn dispatch(owner: &mut Option<owner::Owner>, request: Request) -> Result<serde_json::Value> {
61
66
  match request.method.as_str() {
62
- "preparePolicy" => encode(decode::<policy::Policy>(request.params)?.prepare()?),
67
+ "preparePolicy" => encode(decode::<managed::Policy>(request.params)?.prepare()?),
63
68
  "openOwner" => {
64
69
  if owner.is_some() {
65
70
  return Err(Error::Conflict);
@@ -99,13 +104,14 @@ fn dispatch(owner: &mut Option<owner::Owner>, request: Request) -> Result<serde_
99
104
 
100
105
  fn main() {
101
106
  let arguments = std::env::args().skip(1).collect::<Vec<_>>();
102
- let descriptor = match arguments.as_slice() {
103
- [management] if management == "--management" => None,
107
+ let (docker_mode, descriptor) = match arguments.as_slice() {
108
+ [management] if management == "--management" => (false, None),
109
+ [management] if management == "--docker-forwarding" => (true, None),
104
110
  [management, option, fd]
105
- if management == "--management" && option == "--network-namespace-fd" =>
111
+ if (management == "--management" || management == "--docker-forwarding") && option == "--network-namespace-fd" =>
106
112
  {
107
113
  match fd.parse::<i32>() {
108
- Ok(value) => Some(value),
114
+ Ok(value) => (management == "--docker-forwarding", Some(value)),
109
115
  Err(_) => std::process::exit(2),
110
116
  }
111
117
  }
@@ -123,6 +129,7 @@ fn main() {
123
129
  output.flush().unwrap();
124
130
  let mut input = std::io::stdin().lock();
125
131
  let mut owner = None;
132
+ let mut docker = None;
126
133
  loop {
127
134
  let mut bytes = Vec::new();
128
135
  let read = std::io::Read::by_ref(&mut input)
@@ -142,7 +149,7 @@ fn main() {
142
149
  std::process::exit(2);
143
150
  }
144
151
  let id = request.id.clone();
145
- let response = match dispatch(&mut owner, request) {
152
+ let response = match if docker_mode { docker::dispatch(&mut docker, request) } else { dispatch(&mut owner, request) } {
146
153
  Ok(result) => serde_json::json!({"id":id,"success":true,"result":result}),
147
154
  Err(error) => {
148
155
  serde_json::json!({"id":id,"success":false,"error":"Managed policy operation failed.","errorCode":error.code()})
@@ -0,0 +1,105 @@
1
+ use crate::{egress, policy, wire, Result};
2
+ use serde::{Deserialize, Serialize};
3
+
4
+ /// The v1 object remains byte-for-byte unchanged inside this input union.
5
+ #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
6
+ #[serde(untagged)]
7
+ pub enum Policy {
8
+ Private(policy::Policy),
9
+ Egress(egress::Policy),
10
+ }
11
+
12
+ #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
13
+ #[serde(rename_all = "camelCase", deny_unknown_fields)]
14
+ pub struct Prepared {
15
+ pub policy: Policy,
16
+ pub digest: String,
17
+ }
18
+
19
+ impl Policy {
20
+ pub fn revision(&self) -> u64 {
21
+ match self {
22
+ Self::Private(value) => value.revision,
23
+ Self::Egress(value) => value.revision,
24
+ }
25
+ }
26
+ pub fn kind(&self) -> &'static str {
27
+ match self {
28
+ Self::Private(_) => "privateV1",
29
+ Self::Egress(egress::Policy {
30
+ scope: egress::Scope::RouterEgress(_),
31
+ ..
32
+ }) => "routerEgressV2",
33
+ Self::Egress(egress::Policy {
34
+ scope: egress::Scope::HostTransit(_),
35
+ ..
36
+ }) => "hostTransitV2",
37
+ }
38
+ }
39
+ pub fn prepare(self) -> Result<Prepared> {
40
+ match self {
41
+ Self::Private(value) => Ok(value.prepare()?.into()),
42
+ Self::Egress(value) => Ok(value.prepare()?.into()),
43
+ }
44
+ }
45
+ }
46
+ impl From<policy::Prepared> for Prepared {
47
+ fn from(value: policy::Prepared) -> Self {
48
+ Self {
49
+ policy: Policy::Private(value.policy),
50
+ digest: value.digest,
51
+ }
52
+ }
53
+ }
54
+ impl From<egress::Prepared> for Prepared {
55
+ fn from(value: egress::Prepared) -> Self {
56
+ Self {
57
+ policy: Policy::Egress(value.policy),
58
+ digest: value.digest,
59
+ }
60
+ }
61
+ }
62
+ impl Prepared {
63
+ pub fn validate(&self) -> Result<()> {
64
+ match &self.policy {
65
+ Policy::Private(policy) => policy::Prepared {
66
+ policy: policy.clone(),
67
+ digest: self.digest.clone(),
68
+ }
69
+ .validate(),
70
+ Policy::Egress(policy) => egress::Prepared {
71
+ policy: policy.clone(),
72
+ digest: self.digest.clone(),
73
+ }
74
+ .validate(),
75
+ }
76
+ }
77
+ pub fn program(&self, table: &str) -> Result<Vec<(u16, Vec<wire::Attr>)>> {
78
+ match &self.policy {
79
+ Policy::Private(policy) => policy::Prepared {
80
+ policy: policy.clone(),
81
+ digest: self.digest.clone(),
82
+ }
83
+ .program(table),
84
+ Policy::Egress(policy) => egress::Prepared {
85
+ policy: policy.clone(),
86
+ digest: self.digest.clone(),
87
+ }
88
+ .program(table),
89
+ }
90
+ }
91
+ pub fn validate_interfaces(&self) -> Result<()> {
92
+ match &self.policy {
93
+ Policy::Private(policy) => policy::Prepared {
94
+ policy: policy.clone(),
95
+ digest: self.digest.clone(),
96
+ }
97
+ .validate_interfaces(),
98
+ Policy::Egress(policy) => egress::Prepared {
99
+ policy: policy.clone(),
100
+ digest: self.digest.clone(),
101
+ }
102
+ .validate_interfaces(),
103
+ }
104
+ }
105
+ }