@push.rocks/smartnftables 4.3.1 → 4.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/changelog.md +17 -0
- package/dist_rust/smartnftables_linux_amd64_musl +0 -0
- package/dist_rust/smartnftables_linux_amd64_musl.tsrust-build.json +5 -5
- package/dist_rust/smartnftables_linux_arm64_musl +0 -0
- package/dist_rust/smartnftables_linux_arm64_musl.tsrust-build.json +5 -5
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/managed.egress.types.d.ts +25 -4
- package/package.json +4 -4
- package/readme.md +89 -2
- package/rust/src/egress.compile.rs +105 -38
- package/rust/src/egress.hostgrant.rs +74 -34
- package/rust/src/egress.keys.rs +383 -52
- package/rust/src/egress.leased.rs +71 -42
- package/rust/src/egress.poolguard.rs +72 -0
- package/rust/src/egress.private.rs +32 -37
- package/rust/src/egress.published.rs +74 -47
- package/rust/src/egress.router.rs +1 -1
- package/rust/src/egress.rs +65 -1
- package/rust/src/egress.workloadgrant.rs +30 -28
- package/rust/src/egress_hostgrant_tests.rs +12 -0
- package/rust/src/egress_keys_tests.rs +150 -0
- package/rust/src/egress_poolguardpublished_tests.rs +418 -0
- package/rust/src/egress_tests.rs +47 -4
- package/rust/src/owner.rs +96 -28
- package/rust/src/owner_listing_tests.rs +634 -0
- package/rust/src/owner_tests.rs +3 -0
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/managed.egress.types.ts +26 -4
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
//! Publications through the allocation-pool guard: a publication translates an
|
|
2
|
+
//! inbound flow from the uplink into a pool (the router's transit address), which
|
|
3
|
+
//! the guard denies by its current destination and, on the reply, by its current
|
|
4
|
+
//! source. A guard publication admits exactly that forwarded flow and its replies;
|
|
5
|
+
//! every other tuple meets the decision of the guard without it.
|
|
6
|
+
use super::hostgrant_tests::{
|
|
7
|
+
bytes, chains, decide, granted, host_grant, normalized, prepared, Flow, ACK, ESTABLISHED,
|
|
8
|
+
};
|
|
9
|
+
use super::tests::pool_guard;
|
|
10
|
+
use super::Prepared;
|
|
11
|
+
use serde_json::{json, Value};
|
|
12
|
+
|
|
13
|
+
const NEW: u32 = 8;
|
|
14
|
+
const SYN: u8 = 0x02;
|
|
15
|
+
const CLIENT: [u8; 4] = [198, 51, 100, 7];
|
|
16
|
+
const UPLINK: [u8; 4] = [192, 0, 2, 2];
|
|
17
|
+
const TRANSIT: [u8; 4] = [10, 240, 0, 1];
|
|
18
|
+
const ENS18: Option<(u32, &str)> = Some((2, "ens18"));
|
|
19
|
+
const HANDOFF: Option<(u32, &str)> = Some((4, "handoff"));
|
|
20
|
+
|
|
21
|
+
fn port(protocol: &str, first: u16, end: Option<u16>, target: u16, address: &str) -> Value {
|
|
22
|
+
let mut value =
|
|
23
|
+
json!({"protocol":protocol,"hostPort":first,"targetPort":target,"targetAddress":address});
|
|
24
|
+
if let Some(end) = end {
|
|
25
|
+
value["hostPortEnd"] = json!(end);
|
|
26
|
+
}
|
|
27
|
+
value
|
|
28
|
+
}
|
|
29
|
+
fn published(mut value: Value, ports: Value) -> Value {
|
|
30
|
+
value["scope"]["publishedPorts"] = ports;
|
|
31
|
+
value
|
|
32
|
+
}
|
|
33
|
+
/// One port translated to another, and a range that keeps every port.
|
|
34
|
+
fn fixture() -> Value {
|
|
35
|
+
published(
|
|
36
|
+
pool_guard(),
|
|
37
|
+
json!([
|
|
38
|
+
port("tcp", 443, None, 8443, "10.240.0.1"),
|
|
39
|
+
port("udp", 20000, Some(20200), 20000, "10.240.0.1")
|
|
40
|
+
]),
|
|
41
|
+
)
|
|
42
|
+
}
|
|
43
|
+
/// The opening of an inbound publication as the forward hook sees it: after the
|
|
44
|
+
/// destination NAT to the transit address, the original destination still the uplink.
|
|
45
|
+
fn inbound(protocol: u8, outside: u16, inside: u16) -> Flow {
|
|
46
|
+
Flow {
|
|
47
|
+
input: ENS18,
|
|
48
|
+
output: HANDOFF,
|
|
49
|
+
protocol,
|
|
50
|
+
source: CLIENT,
|
|
51
|
+
destination: TRANSIT,
|
|
52
|
+
source_port: 40000,
|
|
53
|
+
destination_port: inside,
|
|
54
|
+
tcp_flags: if protocol == 6 { SYN } else { 0 },
|
|
55
|
+
state: NEW,
|
|
56
|
+
reply: false,
|
|
57
|
+
zone: 0,
|
|
58
|
+
original: (CLIENT, UPLINK, 40000, outside),
|
|
59
|
+
uid: None,
|
|
60
|
+
label: [0; 16],
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/// The workload's reply as the forward hook sees it, before the reverse NAT.
|
|
64
|
+
fn reply(flow: Flow) -> Flow {
|
|
65
|
+
Flow {
|
|
66
|
+
input: flow.output,
|
|
67
|
+
output: flow.input,
|
|
68
|
+
source: flow.destination,
|
|
69
|
+
destination: flow.source,
|
|
70
|
+
source_port: flow.destination_port,
|
|
71
|
+
destination_port: flow.source_port,
|
|
72
|
+
tcp_flags: if flow.protocol == 6 { ACK } else { 0 },
|
|
73
|
+
state: ESTABLISHED,
|
|
74
|
+
reply: true,
|
|
75
|
+
..flow
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/// Every admitted variant: the opening, the established original direction and
|
|
79
|
+
/// the reply, for the one-port and every boundary of the range.
|
|
80
|
+
fn admitted() -> Vec<Flow> {
|
|
81
|
+
let mut flows = Vec::new();
|
|
82
|
+
for opening in [
|
|
83
|
+
inbound(6, 443, 8443),
|
|
84
|
+
inbound(17, 20000, 20000),
|
|
85
|
+
inbound(17, 20100, 20100),
|
|
86
|
+
inbound(17, 20200, 20200),
|
|
87
|
+
] {
|
|
88
|
+
flows.push(opening);
|
|
89
|
+
flows.push(Flow {
|
|
90
|
+
state: ESTABLISHED,
|
|
91
|
+
tcp_flags: if opening.protocol == 6 { ACK } else { 0 },
|
|
92
|
+
..opening
|
|
93
|
+
});
|
|
94
|
+
flows.push(reply(opening));
|
|
95
|
+
}
|
|
96
|
+
flows
|
|
97
|
+
}
|
|
98
|
+
/// Tuples the publications do not name, each of which the guard alone denies.
|
|
99
|
+
fn foreign() -> Vec<Flow> {
|
|
100
|
+
let tcp = inbound(6, 443, 8443);
|
|
101
|
+
let udp = inbound(17, 20000, 20000);
|
|
102
|
+
vec![
|
|
103
|
+
// An original destination in a pool: no translation from outside every pool.
|
|
104
|
+
Flow {
|
|
105
|
+
original: (CLIENT, [10, 240, 0, 9], 40000, 443),
|
|
106
|
+
..tcp
|
|
107
|
+
},
|
|
108
|
+
Flow {
|
|
109
|
+
original: (CLIENT, TRANSIT, 40000, 8443),
|
|
110
|
+
..tcp
|
|
111
|
+
},
|
|
112
|
+
reply(Flow {
|
|
113
|
+
original: (CLIENT, [10, 241, 0, 9], 40000, 20000),
|
|
114
|
+
..udp
|
|
115
|
+
}),
|
|
116
|
+
// Another target address, target port, published port or protocol.
|
|
117
|
+
Flow {
|
|
118
|
+
destination: [10, 240, 0, 3],
|
|
119
|
+
..tcp
|
|
120
|
+
},
|
|
121
|
+
Flow {
|
|
122
|
+
destination_port: 8444,
|
|
123
|
+
..tcp
|
|
124
|
+
},
|
|
125
|
+
Flow {
|
|
126
|
+
original: (CLIENT, UPLINK, 40000, 444),
|
|
127
|
+
..tcp
|
|
128
|
+
},
|
|
129
|
+
Flow {
|
|
130
|
+
original: (CLIENT, UPLINK, 40000, 8443),
|
|
131
|
+
..tcp
|
|
132
|
+
},
|
|
133
|
+
Flow {
|
|
134
|
+
protocol: 17,
|
|
135
|
+
tcp_flags: 0,
|
|
136
|
+
..tcp
|
|
137
|
+
},
|
|
138
|
+
Flow {
|
|
139
|
+
protocol: 6,
|
|
140
|
+
tcp_flags: SYN,
|
|
141
|
+
..udp
|
|
142
|
+
},
|
|
143
|
+
inbound(17, 19999, 19999),
|
|
144
|
+
inbound(17, 20201, 20201),
|
|
145
|
+
reply(inbound(17, 20201, 20201)),
|
|
146
|
+
// Another conntrack zone, a non-SYN opening, a reply that is not ESTABLISHED
|
|
147
|
+
// and the reply direction carrying the original direction's tuple.
|
|
148
|
+
Flow { zone: 7, ..tcp },
|
|
149
|
+
reply(Flow { zone: 7, ..udp }),
|
|
150
|
+
Flow {
|
|
151
|
+
tcp_flags: ACK,
|
|
152
|
+
..tcp
|
|
153
|
+
},
|
|
154
|
+
Flow {
|
|
155
|
+
state: NEW,
|
|
156
|
+
..reply(tcp)
|
|
157
|
+
},
|
|
158
|
+
]
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
#[test]
|
|
162
|
+
fn pool_guard_publications_admit_exactly_the_forwarded_flow_and_its_reply() {
|
|
163
|
+
let baseline = prepared(pool_guard());
|
|
164
|
+
let with = prepared(fixture());
|
|
165
|
+
for flow in admitted() {
|
|
166
|
+
assert_eq!(
|
|
167
|
+
decide(&baseline, "forward", &flow).verdict,
|
|
168
|
+
0,
|
|
169
|
+
"{:?} baseline",
|
|
170
|
+
flow.destination_port
|
|
171
|
+
);
|
|
172
|
+
let decision = decide(&with, "forward", &flow);
|
|
173
|
+
assert_eq!(
|
|
174
|
+
(decision.verdict, decision.chain.as_str()),
|
|
175
|
+
(1, "pool_guard"),
|
|
176
|
+
"{:?}",
|
|
177
|
+
flow.destination_port
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
for (index, flow) in foreign().iter().enumerate() {
|
|
181
|
+
assert_eq!(
|
|
182
|
+
decide(&baseline, "forward", flow).verdict,
|
|
183
|
+
0,
|
|
184
|
+
"variant {index} baseline"
|
|
185
|
+
);
|
|
186
|
+
assert_eq!(
|
|
187
|
+
decide(&with, "forward", flow),
|
|
188
|
+
decide(&baseline, "forward", flow),
|
|
189
|
+
"variant {index}"
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
// The reply direction carrying the original direction's tuple is not from a
|
|
193
|
+
// pool, so the guard passes it with or without publications.
|
|
194
|
+
let turned = Flow {
|
|
195
|
+
reply: true,
|
|
196
|
+
state: ESTABLISHED,
|
|
197
|
+
..inbound(6, 443, 8443)
|
|
198
|
+
};
|
|
199
|
+
assert_eq!(
|
|
200
|
+
decide(&with, "forward", &turned),
|
|
201
|
+
decide(&baseline, "forward", &turned)
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
#[test]
|
|
206
|
+
fn pool_guard_publications_add_only_their_admissions_to_the_guard_chain() {
|
|
207
|
+
let baseline = prepared(pool_guard());
|
|
208
|
+
let with = prepared(fixture());
|
|
209
|
+
let graph = chains(&with);
|
|
210
|
+
let reference = chains(&baseline);
|
|
211
|
+
assert_eq!(
|
|
212
|
+
graph.keys().collect::<Vec<_>>(),
|
|
213
|
+
reference.keys().collect::<Vec<_>>()
|
|
214
|
+
);
|
|
215
|
+
for name in ["input", "forward", "output", "pool_reply"] {
|
|
216
|
+
assert_eq!(graph[name], reference[name], "{name}");
|
|
217
|
+
}
|
|
218
|
+
// Whatever the number of publications: the ESTABLISHED admission and one
|
|
219
|
+
// opening per protocol, right after the original-destination denial of every pool.
|
|
220
|
+
let pools = 2;
|
|
221
|
+
assert_eq!(
|
|
222
|
+
graph["pool_guard"].len(),
|
|
223
|
+
reference["pool_guard"].len() + 1 + 2
|
|
224
|
+
);
|
|
225
|
+
assert_eq!(
|
|
226
|
+
graph["pool_guard"][..pools],
|
|
227
|
+
reference["pool_guard"][..pools]
|
|
228
|
+
);
|
|
229
|
+
assert_eq!(
|
|
230
|
+
graph["pool_guard"][pools + 3..],
|
|
231
|
+
reference["pool_guard"][pools..]
|
|
232
|
+
);
|
|
233
|
+
// A host-local flow with a publication's tuples, translated from outside every
|
|
234
|
+
// pool, is the same publication on the local hooks.
|
|
235
|
+
for flow in admitted() {
|
|
236
|
+
for (chain, local) in [
|
|
237
|
+
(
|
|
238
|
+
"input",
|
|
239
|
+
Flow {
|
|
240
|
+
output: None,
|
|
241
|
+
..flow
|
|
242
|
+
},
|
|
243
|
+
),
|
|
244
|
+
(
|
|
245
|
+
"output",
|
|
246
|
+
Flow {
|
|
247
|
+
input: None,
|
|
248
|
+
..flow
|
|
249
|
+
},
|
|
250
|
+
),
|
|
251
|
+
] {
|
|
252
|
+
assert_eq!(
|
|
253
|
+
decide(&baseline, chain, &local).verdict,
|
|
254
|
+
0,
|
|
255
|
+
"{chain} baseline"
|
|
256
|
+
);
|
|
257
|
+
assert_eq!(decide(&with, chain, &local).verdict, 1, "{chain}");
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
#[test]
|
|
263
|
+
fn pool_guard_publications_absent_and_empty_keep_the_digest_and_compiled_bytes() {
|
|
264
|
+
let baseline = prepared(pool_guard());
|
|
265
|
+
assert!(!serde_json::to_string(&baseline.policy)
|
|
266
|
+
.unwrap()
|
|
267
|
+
.contains("publishedPorts"));
|
|
268
|
+
let empty = prepared(published(pool_guard(), json!([])));
|
|
269
|
+
assert_eq!(empty, baseline);
|
|
270
|
+
assert_eq!(bytes(&empty), bytes(&baseline));
|
|
271
|
+
let with = prepared(fixture());
|
|
272
|
+
assert_ne!(with.digest, baseline.digest);
|
|
273
|
+
// Canonical order: a reordered list is the same policy.
|
|
274
|
+
let reordered = prepared(published(
|
|
275
|
+
pool_guard(),
|
|
276
|
+
json!([
|
|
277
|
+
port("udp", 20000, Some(20200), 20000, "10.240.0.1"),
|
|
278
|
+
port("tcp", 443, None, 8443, "10.240.0.1")
|
|
279
|
+
]),
|
|
280
|
+
));
|
|
281
|
+
assert_eq!(reordered, with);
|
|
282
|
+
// Host grants and publications are independent exceptions of one guard.
|
|
283
|
+
let both = prepared(granted(
|
|
284
|
+
fixture(),
|
|
285
|
+
json!([host_grant("tcp", "10.240.0.1", "10.241.0.2", 8080)]),
|
|
286
|
+
));
|
|
287
|
+
for flow in admitted() {
|
|
288
|
+
assert_eq!(decide(&both, "forward", &flow).verdict, 1);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
#[test]
|
|
293
|
+
fn pool_guard_publications_refuse_every_entry_outside_the_contract() {
|
|
294
|
+
let refused = |ports: Value| normalized(published(pool_guard(), ports)).is_err();
|
|
295
|
+
// A target outside every pool needs no exception and is no publication into one.
|
|
296
|
+
assert!(refused(json!([port("tcp", 443, None, 8443, "10.250.0.1")])));
|
|
297
|
+
assert!(refused(json!([port(
|
|
298
|
+
"tcp",
|
|
299
|
+
443,
|
|
300
|
+
None,
|
|
301
|
+
8443,
|
|
302
|
+
"10.240.0.0/16"
|
|
303
|
+
)])));
|
|
304
|
+
assert!(refused(json!([port("tcp", 443, None, 8443, "127.0.0.1")])));
|
|
305
|
+
assert!(refused(json!([port(
|
|
306
|
+
"sctp",
|
|
307
|
+
443,
|
|
308
|
+
None,
|
|
309
|
+
8443,
|
|
310
|
+
"10.240.0.1"
|
|
311
|
+
)])));
|
|
312
|
+
assert!(refused(json!([port("tcp", 0, None, 8443, "10.240.0.1")])));
|
|
313
|
+
assert!(refused(json!([port("tcp", 443, None, 0, "10.240.0.1")])));
|
|
314
|
+
// A range keeps every port and runs upward; one port has no end.
|
|
315
|
+
assert!(refused(json!([port(
|
|
316
|
+
"udp",
|
|
317
|
+
20000,
|
|
318
|
+
Some(20200),
|
|
319
|
+
30000,
|
|
320
|
+
"10.240.0.1"
|
|
321
|
+
)])));
|
|
322
|
+
assert!(refused(json!([port(
|
|
323
|
+
"udp",
|
|
324
|
+
20000,
|
|
325
|
+
Some(20000),
|
|
326
|
+
20000,
|
|
327
|
+
"10.240.0.1"
|
|
328
|
+
)])));
|
|
329
|
+
assert!(refused(json!([port(
|
|
330
|
+
"udp",
|
|
331
|
+
20000,
|
|
332
|
+
Some(19999),
|
|
333
|
+
20000,
|
|
334
|
+
"10.240.0.1"
|
|
335
|
+
)])));
|
|
336
|
+
// One protocol and port is published once.
|
|
337
|
+
assert!(refused(json!([
|
|
338
|
+
port("tcp", 443, None, 8443, "10.240.0.1"),
|
|
339
|
+
port("tcp", 443, None, 8444, "10.240.0.1")
|
|
340
|
+
])));
|
|
341
|
+
assert!(refused(json!([
|
|
342
|
+
port("udp", 20000, Some(20200), 20000, "10.240.0.1"),
|
|
343
|
+
port("udp", 20200, None, 5060, "10.240.0.1")
|
|
344
|
+
])));
|
|
345
|
+
assert!(!refused(json!([
|
|
346
|
+
port("tcp", 443, None, 8443, "10.240.0.1"),
|
|
347
|
+
port("udp", 443, None, 8443, "10.240.0.1")
|
|
348
|
+
])));
|
|
349
|
+
let mut unknown = port("tcp", 443, None, 8443, "10.240.0.1");
|
|
350
|
+
unknown["hostIp"] = json!("192.0.2.2");
|
|
351
|
+
assert!(refused(json!([unknown])));
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
#[test]
|
|
355
|
+
fn pool_guard_publications_are_bounded_and_fit_the_largest_guard() {
|
|
356
|
+
let many = |count: u16| -> Value {
|
|
357
|
+
json!((0..count)
|
|
358
|
+
.map(|index| port("tcp", 1000 + index, None, 1000 + index, "10.240.0.1"))
|
|
359
|
+
.collect::<Vec<_>>())
|
|
360
|
+
};
|
|
361
|
+
match normalized(published(pool_guard(), many(1025))) {
|
|
362
|
+
Err(crate::Error::Exhausted(bound)) => assert_eq!(bound.name, "publishedPorts"),
|
|
363
|
+
other => panic!("{other:?}"),
|
|
364
|
+
}
|
|
365
|
+
// The largest guard: 64 pools, 1024 host grants, 1024 publications and every
|
|
366
|
+
// loopback port owner compile inside the atomic budget.
|
|
367
|
+
let mut guard = pool_guard();
|
|
368
|
+
guard["scope"]["prefixes"] = json!((0..64)
|
|
369
|
+
.map(|part| format!("10.{part}.0.0/16"))
|
|
370
|
+
.collect::<Vec<_>>());
|
|
371
|
+
let grants: Vec<_> = (0..1024_u16)
|
|
372
|
+
.map(|index| {
|
|
373
|
+
host_grant(
|
|
374
|
+
if index % 2 == 0 { "tcp" } else { "udp" },
|
|
375
|
+
"10.40.0.1",
|
|
376
|
+
"10.41.0.2",
|
|
377
|
+
20000 + index / 2,
|
|
378
|
+
)
|
|
379
|
+
})
|
|
380
|
+
.collect();
|
|
381
|
+
guard["scope"]["hostGrants"] = json!(grants);
|
|
382
|
+
guard["scope"]["localTcpPortOwners"] = json!((0..8)
|
|
383
|
+
.map(|index| json!({"address":"127.0.0.1","port":10010 + index,"uid":0}))
|
|
384
|
+
.collect::<Vec<_>>());
|
|
385
|
+
let ports: Vec<_> = (0..1024_u16)
|
|
386
|
+
.map(|index| {
|
|
387
|
+
port(
|
|
388
|
+
if index % 2 == 0 { "tcp" } else { "udp" },
|
|
389
|
+
1000 + index / 2,
|
|
390
|
+
None,
|
|
391
|
+
1000 + index / 2,
|
|
392
|
+
"10.40.0.1",
|
|
393
|
+
)
|
|
394
|
+
})
|
|
395
|
+
.collect();
|
|
396
|
+
let largest: Prepared = prepared(published(guard, json!(ports)));
|
|
397
|
+
let program = largest
|
|
398
|
+
.program(&format!("snft_{}", "x".repeat(59)))
|
|
399
|
+
.unwrap();
|
|
400
|
+
let size = |kinds: &[u16]| -> usize {
|
|
401
|
+
program
|
|
402
|
+
.iter()
|
|
403
|
+
.filter(|(kind, _)| kinds.contains(kind))
|
|
404
|
+
.map(|(_, attributes)| crate::wire::encode_attrs(attributes).len() + 20)
|
|
405
|
+
.sum()
|
|
406
|
+
};
|
|
407
|
+
let (graph, elements) = (size(&[3, 6, 9]), size(&[12]));
|
|
408
|
+
assert!(
|
|
409
|
+
program.len() < 768
|
|
410
|
+
&& graph <= super::compile::RULE_BYTES
|
|
411
|
+
&& graph + elements <= super::compile::TARGET_BYTES
|
|
412
|
+
);
|
|
413
|
+
assert!(768 * (20 + 72 + 36 + 12) + graph + elements + 64 <= crate::wire::BATCH_BYTES);
|
|
414
|
+
println!(
|
|
415
|
+
"POOL_GUARD_PUBLISHED_MAX prefixes=64 grants=1024 owners=8 published=1024 operations={} rule_bytes={graph} element_bytes={elements}",
|
|
416
|
+
program.len()
|
|
417
|
+
);
|
|
418
|
+
}
|
package/rust/src/egress_tests.rs
CHANGED
|
@@ -507,8 +507,27 @@ fn prepared(value: Value) -> Prepared {
|
|
|
507
507
|
normalized(value).unwrap().prepare().unwrap()
|
|
508
508
|
}
|
|
509
509
|
fn program_bytes(prepared: &Prepared, table: &str) -> Vec<u8> {
|
|
510
|
+
encoded(prepared.program(table).unwrap())
|
|
511
|
+
}
|
|
512
|
+
/// LEGACY(<=4.4.x): the program as releases before typed sets compiled it,
|
|
513
|
+
/// whose goldens it must still reproduce byte for byte, so their kernel graphs
|
|
514
|
+
/// stay adoptable.
|
|
515
|
+
fn untyped_program_bytes(prepared: &Prepared, table: &str) -> Vec<u8> {
|
|
516
|
+
encoded(
|
|
517
|
+
prepared
|
|
518
|
+
.program(table)
|
|
519
|
+
.unwrap()
|
|
520
|
+
.into_iter()
|
|
521
|
+
.map(|(kind, attributes)| match kind {
|
|
522
|
+
9 => (kind, crate::owner::untyped_set(&attributes)),
|
|
523
|
+
_ => (kind, attributes),
|
|
524
|
+
})
|
|
525
|
+
.collect(),
|
|
526
|
+
)
|
|
527
|
+
}
|
|
528
|
+
fn encoded(program: Vec<(u16, Vec<crate::wire::Attr>)>) -> Vec<u8> {
|
|
510
529
|
let mut bytes = Vec::new();
|
|
511
|
-
for (kind, attributes) in
|
|
530
|
+
for (kind, attributes) in program {
|
|
512
531
|
let encoded = crate::wire::encode_attrs(&attributes);
|
|
513
532
|
bytes.extend(kind.to_be_bytes());
|
|
514
533
|
bytes.extend((encoded.len() as u32).to_be_bytes());
|
|
@@ -606,12 +625,20 @@ fn host_v2_published_ports_compile_uplink_dnat_and_exact_forward_admission() {
|
|
|
606
625
|
for (_, expressions) in &forward[..4] {
|
|
607
626
|
assert_eq!(expressions[expressions.len() - 2], "lookup");
|
|
608
627
|
}
|
|
609
|
-
|
|
628
|
+
// The graph of 4.4.x, byte for byte, once its sets are untyped again.
|
|
629
|
+
let bytes = untyped_program_bytes(&published, "snft_v2_host_golden");
|
|
610
630
|
assert_eq!(bytes.len(), 30988);
|
|
611
631
|
assert_eq!(
|
|
612
632
|
format!("{:x}", Sha256::digest(&bytes)),
|
|
613
633
|
"b36f359c6439e9fcd77c291c7dac7854014225d3de1f8a4e49df6fec6ae570c4"
|
|
614
634
|
);
|
|
635
|
+
// Re-frozen with typed sets in 4.5.0.
|
|
636
|
+
let bytes = program_bytes(&published, "snft_v2_host_golden");
|
|
637
|
+
assert_eq!(bytes.len(), 31168);
|
|
638
|
+
assert_eq!(
|
|
639
|
+
format!("{:x}", Sha256::digest(&bytes)),
|
|
640
|
+
"609f2388e3c5cf45b921182c17252c072c4db2f07e3cc605e282faddd937d458"
|
|
641
|
+
);
|
|
615
642
|
}
|
|
616
643
|
|
|
617
644
|
#[test]
|
|
@@ -724,12 +751,20 @@ fn router_v2_without_published_ports_keeps_its_exact_digest_and_compiled_bytes()
|
|
|
724
751
|
);
|
|
725
752
|
// Re-frozen with the set-backed router graph of 3.0.0: the digest is the
|
|
726
753
|
// policy's and did not change, the compiled graph did.
|
|
727
|
-
|
|
754
|
+
// The graph of 4.4.x, byte for byte, once its sets are untyped again.
|
|
755
|
+
let bytes = untyped_program_bytes(&baseline, "snft_v2_router_golden");
|
|
728
756
|
assert_eq!(bytes.len(), 47674);
|
|
729
757
|
assert_eq!(
|
|
730
758
|
format!("{:x}", Sha256::digest(&bytes)),
|
|
731
759
|
"ea609865f32031f6835ef4fc7f67f5754a15f466580308bdccc9db87d4ccd2c5"
|
|
732
760
|
);
|
|
761
|
+
// Re-frozen with typed sets in 4.5.0.
|
|
762
|
+
let bytes = program_bytes(&baseline, "snft_v2_router_golden");
|
|
763
|
+
assert_eq!(bytes.len(), 48362);
|
|
764
|
+
assert_eq!(
|
|
765
|
+
format!("{:x}", Sha256::digest(&bytes)),
|
|
766
|
+
"47280ecb596125d6421b1ab42e2d768e53844f55d7d0f130fe31a8cd5b9a5668"
|
|
767
|
+
);
|
|
733
768
|
let serialized = serde_json::to_string(&baseline.policy).unwrap();
|
|
734
769
|
assert!(!serialized.contains("publishedPorts"));
|
|
735
770
|
// An explicit empty publication is the same canonical policy, digest and graph.
|
|
@@ -796,12 +831,20 @@ fn router_v2_published_ports_compile_handoff_dnat_classification_and_admission()
|
|
|
796
831
|
assert!(incoming[4..]
|
|
797
832
|
.iter()
|
|
798
833
|
.any(|expressions| expressions.contains(&"ct".to_string())));
|
|
799
|
-
|
|
834
|
+
// The graph of 4.4.x, byte for byte, once its sets are untyped again.
|
|
835
|
+
let bytes = untyped_program_bytes(&published, "snft_v2_router_golden");
|
|
800
836
|
assert_eq!(bytes.len(), 55494);
|
|
801
837
|
assert_eq!(
|
|
802
838
|
format!("{:x}", Sha256::digest(&bytes)),
|
|
803
839
|
"60a7ba1c15d49e5ff54cfbf9fbe2d72f03973acd1a897eef2d975547ef4abf5b"
|
|
804
840
|
);
|
|
841
|
+
// Re-frozen with typed sets in 4.5.0.
|
|
842
|
+
let bytes = program_bytes(&published, "snft_v2_router_golden");
|
|
843
|
+
assert_eq!(bytes.len(), 56454);
|
|
844
|
+
assert_eq!(
|
|
845
|
+
format!("{:x}", Sha256::digest(&bytes)),
|
|
846
|
+
"1d2a72707bc10ed689e3f9b4c1cb3e798e89c01fc97115078dcd32e891b22306"
|
|
847
|
+
);
|
|
805
848
|
}
|
|
806
849
|
|
|
807
850
|
#[test]
|