@push.rocks/smartnftables 2.5.2 → 3.0.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 +31 -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 +69 -3
- package/package.json +6 -6
- package/readme.md +342 -61
- package/rust/src/egress.compile.rs +157 -76
- package/rust/src/egress.host.rs +104 -44
- package/rust/src/egress.hostgrant.rs +14 -14
- package/rust/src/egress.keys.rs +305 -0
- package/rust/src/egress.leased.rs +399 -0
- package/rust/src/egress.poolguard.rs +45 -0
- package/rust/src/egress.private.rs +197 -0
- package/rust/src/egress.published.rs +370 -0
- package/rust/src/egress.router.rs +64 -295
- package/rust/src/egress.rs +248 -17
- package/rust/src/egress.workloadgrant.rs +88 -0
- package/rust/src/egress_hostgrant_tests.rs +196 -47
- package/rust/src/egress_localplatform_tests.rs +204 -0
- package/rust/src/egress_localport_tests.rs +266 -0
- package/rust/src/egress_publishedrange_tests.rs +812 -0
- package/rust/src/egress_routerequivalence_tests.rs +448 -0
- package/rust/src/egress_tests.rs +132 -230
- package/rust/src/egress_workloadgrant_tests.rs +313 -0
- package/rust/src/owner.rs +77 -15
- package/rust/src/owner_host_traffic_tests.rs +4 -0
- package/rust/src/owner_hostgrant_traffic_tests.rs +9 -4
- package/rust/src/owner_identity_tests.rs +228 -80
- package/rust/src/owner_localplatform_tests.rs +274 -0
- package/rust/src/owner_localport_tests.rs +241 -0
- package/rust/src/owner_publishedrange_traffic_tests.rs +306 -0
- package/rust/src/owner_router_traffic_tests.rs +2 -0
- package/rust/src/owner_scale_traffic_tests.rs +383 -0
- package/rust/src/owner_tests.rs +15 -0
- package/rust/src/owner_workloadgrant_tests.rs +278 -0
- package/rust/src/policy.rs +29 -25
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/managed.egress.types.ts +71 -3
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
//! Workload grants: stateful one-way flows between two workload endpoints of
|
|
2
|
+
//! one router scope, with no reverse opening.
|
|
3
|
+
use super::hostgrant_tests::{
|
|
4
|
+
bytes, chains, decide, host_grant, many, normalized, opening, prepared, Flow, ACK, ESTABLISHED,
|
|
5
|
+
};
|
|
6
|
+
use super::tests::{host, pool_guard, router};
|
|
7
|
+
use crate::wire;
|
|
8
|
+
use serde_json::{json, Value};
|
|
9
|
+
|
|
10
|
+
const SOURCE: [u8; 4] = [10, 241, 0, 2];
|
|
11
|
+
const TARGET: [u8; 4] = [10, 241, 1, 2];
|
|
12
|
+
const SOURCE_LINK: Option<(u32, &str)> = Some((2, "work"));
|
|
13
|
+
const TARGET_LINK: Option<(u32, &str)> = Some((5, "target"));
|
|
14
|
+
|
|
15
|
+
/// The router fixture with a second veth workload endpoint, `target`.
|
|
16
|
+
pub(crate) fn two_workloads() -> Value {
|
|
17
|
+
let mut value = router();
|
|
18
|
+
value["scope"]["endpoints"]
|
|
19
|
+
.as_array_mut()
|
|
20
|
+
.unwrap()
|
|
21
|
+
.push(json!(
|
|
22
|
+
{"id":"target","interfaceIndex":5,"interfaceName":"target","interfaceKind":"veth",
|
|
23
|
+
"sourcePrefixes":["10.241.1.2/32"]}));
|
|
24
|
+
value["scope"]["links"].as_array_mut().unwrap().push(json!(
|
|
25
|
+
{"interfaceIndex":5,"interfaceName":"target","interfaceKind":"veth",
|
|
26
|
+
"macAddress":"02:00:00:00:00:05","interfaceLinkIndex":105,
|
|
27
|
+
"requiredIpv4Addresses":["10.241.1.1"]}));
|
|
28
|
+
value
|
|
29
|
+
}
|
|
30
|
+
pub(crate) fn workload_grant(protocol: &str, source: &str, destination: &str, port: u16) -> Value {
|
|
31
|
+
host_grant(protocol, source, destination, port)
|
|
32
|
+
}
|
|
33
|
+
pub(crate) fn granted(mut value: Value, grants: Value) -> Value {
|
|
34
|
+
value["scope"]["workloadGrants"] = grants;
|
|
35
|
+
value
|
|
36
|
+
}
|
|
37
|
+
fn exact() -> Value {
|
|
38
|
+
json!([
|
|
39
|
+
workload_grant("tcp", "10.241.0.2", "10.241.1.2", 8080),
|
|
40
|
+
workload_grant("udp", "10.241.0.2", "10.241.1.2", 5353)
|
|
41
|
+
])
|
|
42
|
+
}
|
|
43
|
+
/// The source workload's opening toward the target, as FORWARD sees it.
|
|
44
|
+
fn dial(protocol: u8, port: u16) -> Flow {
|
|
45
|
+
Flow {
|
|
46
|
+
input: SOURCE_LINK,
|
|
47
|
+
output: TARGET_LINK,
|
|
48
|
+
protocol,
|
|
49
|
+
source: SOURCE,
|
|
50
|
+
destination: TARGET,
|
|
51
|
+
source_port: 40000,
|
|
52
|
+
destination_port: port,
|
|
53
|
+
original: (SOURCE, TARGET, 40000, port),
|
|
54
|
+
..opening(SOURCE_LINK, TARGET_LINK)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
fn answer(flow: Flow) -> Flow {
|
|
58
|
+
Flow {
|
|
59
|
+
input: flow.output,
|
|
60
|
+
output: flow.input,
|
|
61
|
+
source: flow.destination,
|
|
62
|
+
destination: flow.source,
|
|
63
|
+
source_port: flow.destination_port,
|
|
64
|
+
destination_port: flow.source_port,
|
|
65
|
+
tcp_flags: ACK,
|
|
66
|
+
state: ESTABLISHED,
|
|
67
|
+
reply: true,
|
|
68
|
+
..flow
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
#[test]
|
|
73
|
+
fn workload_grants_absent_and_empty_keep_the_router_digest_and_compiled_bytes() {
|
|
74
|
+
let baseline = prepared(two_workloads());
|
|
75
|
+
let empty = prepared(granted(two_workloads(), json!([])));
|
|
76
|
+
assert_eq!(empty, baseline);
|
|
77
|
+
assert_eq!(bytes(&empty), bytes(&baseline));
|
|
78
|
+
let with = prepared(granted(two_workloads(), exact()));
|
|
79
|
+
assert_ne!(with.digest, baseline.digest);
|
|
80
|
+
let mut reversed = exact();
|
|
81
|
+
reversed.as_array_mut().unwrap().reverse();
|
|
82
|
+
assert_eq!(prepared(granted(two_workloads(), reversed)), with);
|
|
83
|
+
// Only the router scope carries workload grants.
|
|
84
|
+
for fixture in [host(), pool_guard()] {
|
|
85
|
+
assert!(normalized(granted(fixture, exact())).is_err());
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
#[test]
|
|
90
|
+
fn workload_grant_opens_one_way_and_admits_only_the_exact_answer() {
|
|
91
|
+
let baseline = prepared(two_workloads());
|
|
92
|
+
let with = prepared(granted(two_workloads(), exact()));
|
|
93
|
+
let decided = |flow: &Flow| {
|
|
94
|
+
(
|
|
95
|
+
decide(&baseline, "forward", flow).verdict,
|
|
96
|
+
decide(&with, "forward", flow).verdict,
|
|
97
|
+
)
|
|
98
|
+
};
|
|
99
|
+
let tcp = dial(6, 8080);
|
|
100
|
+
let udp = dial(17, 5353);
|
|
101
|
+
// The openings, the established original direction and both answers pass;
|
|
102
|
+
// the baseline denies each at the private endpoint denials.
|
|
103
|
+
for flow in [
|
|
104
|
+
tcp,
|
|
105
|
+
Flow {
|
|
106
|
+
state: ESTABLISHED,
|
|
107
|
+
tcp_flags: ACK,
|
|
108
|
+
..tcp
|
|
109
|
+
},
|
|
110
|
+
udp,
|
|
111
|
+
answer(tcp),
|
|
112
|
+
answer(udp),
|
|
113
|
+
] {
|
|
114
|
+
assert_eq!(decided(&flow), (0, 1), "{:?}", flow.original);
|
|
115
|
+
}
|
|
116
|
+
// No reverse opening: the target cannot open toward the source, not even
|
|
117
|
+
// from the granted port to the source's port, and an answer that is not
|
|
118
|
+
// ESTABLISHED is refused.
|
|
119
|
+
let reverse = |port: u16, source_port: u16| Flow {
|
|
120
|
+
reply: false,
|
|
121
|
+
state: 8,
|
|
122
|
+
tcp_flags: 0x02,
|
|
123
|
+
original: (TARGET, SOURCE, source_port, port),
|
|
124
|
+
..answer(Flow {
|
|
125
|
+
source_port: port,
|
|
126
|
+
destination_port: source_port,
|
|
127
|
+
..tcp
|
|
128
|
+
})
|
|
129
|
+
};
|
|
130
|
+
for flow in [
|
|
131
|
+
reverse(40000, 8080),
|
|
132
|
+
reverse(8080, 40000),
|
|
133
|
+
reverse(5353, 40000),
|
|
134
|
+
Flow {
|
|
135
|
+
state: 8,
|
|
136
|
+
..answer(tcp)
|
|
137
|
+
},
|
|
138
|
+
Flow {
|
|
139
|
+
state: 8,
|
|
140
|
+
..answer(udp)
|
|
141
|
+
},
|
|
142
|
+
] {
|
|
143
|
+
assert_eq!(decided(&flow), (0, 0), "{:?}", flow.original);
|
|
144
|
+
}
|
|
145
|
+
// Everything the grant does not name keeps the baseline decision: another
|
|
146
|
+
// port or protocol, a spoofed or renamed link, another address, a flow
|
|
147
|
+
// translated in transit, another zone and a TCP opening without SYN.
|
|
148
|
+
for flow in [
|
|
149
|
+
dial(6, 8081),
|
|
150
|
+
dial(17, 8080),
|
|
151
|
+
dial(6, 5353),
|
|
152
|
+
Flow {
|
|
153
|
+
input: Some((3, "vpn")),
|
|
154
|
+
..tcp
|
|
155
|
+
},
|
|
156
|
+
Flow {
|
|
157
|
+
input: Some((2, "renamed")),
|
|
158
|
+
..tcp
|
|
159
|
+
},
|
|
160
|
+
Flow {
|
|
161
|
+
input: Some((9, "work")),
|
|
162
|
+
..tcp
|
|
163
|
+
},
|
|
164
|
+
Flow {
|
|
165
|
+
output: Some((5, "renamed")),
|
|
166
|
+
..tcp
|
|
167
|
+
},
|
|
168
|
+
Flow {
|
|
169
|
+
destination: [10, 241, 1, 3],
|
|
170
|
+
original: (SOURCE, [10, 241, 1, 3], 40000, 8080),
|
|
171
|
+
..tcp
|
|
172
|
+
},
|
|
173
|
+
Flow {
|
|
174
|
+
original: (SOURCE, TARGET, 40000, 9090),
|
|
175
|
+
..tcp
|
|
176
|
+
},
|
|
177
|
+
Flow {
|
|
178
|
+
original: ([10, 241, 0, 9], TARGET, 40000, 8080),
|
|
179
|
+
..tcp
|
|
180
|
+
},
|
|
181
|
+
Flow { zone: 17, ..tcp },
|
|
182
|
+
Flow {
|
|
183
|
+
tcp_flags: ACK,
|
|
184
|
+
..tcp
|
|
185
|
+
},
|
|
186
|
+
] {
|
|
187
|
+
let (before, after) = decided(&flow);
|
|
188
|
+
assert_eq!(after, before, "{:?} {:?}", flow.input, flow.original);
|
|
189
|
+
}
|
|
190
|
+
// Everything else in the graph is unchanged.
|
|
191
|
+
let graph = chains(&with);
|
|
192
|
+
let reference = chains(&baseline);
|
|
193
|
+
for (name, rules) in &reference {
|
|
194
|
+
if name != "forward" {
|
|
195
|
+
assert_eq!(&graph[name], rules, "{name}");
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
assert_eq!(graph["forward"].len(), reference["forward"].len() + 4);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
#[test]
|
|
202
|
+
fn workload_grants_reject_unbound_same_link_local_platform_and_tun_ends() {
|
|
203
|
+
assert!(normalized(granted(two_workloads(), exact())).is_ok());
|
|
204
|
+
let grant = |source: &str, destination: &str| {
|
|
205
|
+
granted(
|
|
206
|
+
two_workloads(),
|
|
207
|
+
json!([workload_grant("tcp", source, destination, 8080)]),
|
|
208
|
+
)
|
|
209
|
+
};
|
|
210
|
+
let mut tun = two_workloads();
|
|
211
|
+
tun["scope"]["endpoints"][1]["sourcePrefixes"] = json!(["10.242.0.0/24"]);
|
|
212
|
+
for invalid in [
|
|
213
|
+
// An end outside every workload endpoint, or the same endpoint twice.
|
|
214
|
+
grant("10.241.0.2", "10.241.9.2"),
|
|
215
|
+
grant("10.241.9.2", "10.241.1.2"),
|
|
216
|
+
grant("10.241.0.2", "10.241.0.2"),
|
|
217
|
+
// A router-local address or a TUN endpoint is not a veth workload.
|
|
218
|
+
grant("10.241.0.2", "10.241.1.1"),
|
|
219
|
+
grant("10.242.0.2", "10.241.1.2"),
|
|
220
|
+
grant("10.241.0.2", "10.242.0.2"),
|
|
221
|
+
// Prefixes, wildcards, ports and duplicates, as for host grants.
|
|
222
|
+
grant("10.241.0.2", "10.241.1.0/24"),
|
|
223
|
+
grant("0.0.0.0", "10.241.1.2"),
|
|
224
|
+
granted(
|
|
225
|
+
two_workloads(),
|
|
226
|
+
json!([workload_grant("tcp", "10.241.0.2", "10.241.1.2", 0)]),
|
|
227
|
+
),
|
|
228
|
+
granted(
|
|
229
|
+
two_workloads(),
|
|
230
|
+
json!([workload_grant("sctp", "10.241.0.2", "10.241.1.2", 80)]),
|
|
231
|
+
),
|
|
232
|
+
granted(
|
|
233
|
+
two_workloads(),
|
|
234
|
+
json!([
|
|
235
|
+
workload_grant("tcp", "10.241.0.2", "10.241.1.2", 80),
|
|
236
|
+
workload_grant("tcp", "10.241.0.2", "10.241.1.2", 80)
|
|
237
|
+
]),
|
|
238
|
+
),
|
|
239
|
+
granted(
|
|
240
|
+
two_workloads(),
|
|
241
|
+
json!([{"protocol":"tcp","sourceAddress":"10.241.0.2","destinationAddress":"10.241.1.2",
|
|
242
|
+
"destinationPort":80,"bidirectional":true}]),
|
|
243
|
+
),
|
|
244
|
+
granted(
|
|
245
|
+
two_workloads(),
|
|
246
|
+
json!((0..1025_u16)
|
|
247
|
+
.map(|port| workload_grant("tcp", "10.241.0.2", "10.241.1.2", 1 + port))
|
|
248
|
+
.collect::<Vec<_>>()),
|
|
249
|
+
),
|
|
250
|
+
] {
|
|
251
|
+
assert!(
|
|
252
|
+
normalized(invalid.clone()).is_err(),
|
|
253
|
+
"{}",
|
|
254
|
+
invalid["scope"]["workloadGrants"]
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
// Both directions between two workloads are two separate grants.
|
|
258
|
+
assert!(normalized(granted(
|
|
259
|
+
two_workloads(),
|
|
260
|
+
json!([
|
|
261
|
+
workload_grant("tcp", "10.241.0.2", "10.241.1.2", 8080),
|
|
262
|
+
workload_grant("tcp", "10.241.1.2", "10.241.0.2", 8080)
|
|
263
|
+
])
|
|
264
|
+
))
|
|
265
|
+
.is_ok());
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/// 1024 grants fit the atomic reserve with a constant rule graph; the element
|
|
269
|
+
/// budget is shared with the scope's host grants.
|
|
270
|
+
#[test]
|
|
271
|
+
fn workload_grants_meet_the_atomic_reserve_at_the_contract_maximum() {
|
|
272
|
+
let deletion = 768 * (20 + 72 + 36 + 12);
|
|
273
|
+
let maximum: Vec<_> = (0..1024_u16)
|
|
274
|
+
.map(|index| {
|
|
275
|
+
workload_grant(
|
|
276
|
+
if index % 2 == 0 { "tcp" } else { "udp" },
|
|
277
|
+
"10.241.0.2",
|
|
278
|
+
"10.241.1.2",
|
|
279
|
+
20000 + index / 2,
|
|
280
|
+
)
|
|
281
|
+
})
|
|
282
|
+
.collect();
|
|
283
|
+
let small = prepared(granted(two_workloads(), exact()));
|
|
284
|
+
let prepared = prepared(granted(two_workloads(), json!(maximum)));
|
|
285
|
+
assert_eq!(
|
|
286
|
+
chains(&prepared)["forward"].len(),
|
|
287
|
+
chains(&small)["forward"].len()
|
|
288
|
+
);
|
|
289
|
+
let program = prepared
|
|
290
|
+
.program(&format!("snft_{}", "x".repeat(59)))
|
|
291
|
+
.unwrap();
|
|
292
|
+
let size = |kinds: &[u16]| -> usize {
|
|
293
|
+
program
|
|
294
|
+
.iter()
|
|
295
|
+
.filter(|(kind, _)| kinds.contains(kind))
|
|
296
|
+
.map(|(_, attributes)| wire::encode_attrs(attributes).len() + 20)
|
|
297
|
+
.sum()
|
|
298
|
+
};
|
|
299
|
+
let (graph, elements) = (size(&[3, 6, 9]), size(&[12]));
|
|
300
|
+
assert!(program.len() < 768 && graph <= super::compile::RULE_BYTES && graph + elements <= super::compile::TARGET_BYTES);
|
|
301
|
+
assert!(deletion + graph + elements + 64 <= wire::BATCH_BYTES);
|
|
302
|
+
println!(
|
|
303
|
+
"WORKLOAD_GRANT_MAX grants=1024 operations={} rule_bytes={graph} element_bytes={elements}",
|
|
304
|
+
program.len()
|
|
305
|
+
);
|
|
306
|
+
// Host grants and workload grants share the target budget with the rest of
|
|
307
|
+
// the scope's elements; both maxima together fit it.
|
|
308
|
+
let mut both = granted(two_workloads(), json!(maximum));
|
|
309
|
+
both["scope"]["hostGrants"] = many(1024);
|
|
310
|
+
let both = normalized(both).unwrap().prepare().unwrap();
|
|
311
|
+
let (operations, rule_bytes, element_bytes) = super::tests::cost(&both);
|
|
312
|
+
assert!(operations < 768 && rule_bytes + element_bytes <= super::compile::TARGET_BYTES);
|
|
313
|
+
}
|
package/rust/src/owner.rs
CHANGED
|
@@ -680,7 +680,8 @@ fn chain_identity(attributes: &[Attr]) -> Result<Vec<u8>> {
|
|
|
680
680
|
}
|
|
681
681
|
serde_json::to_vec(&wire::canonical(&identity, false)?).map_err(|_| Error::Protocol)
|
|
682
682
|
}
|
|
683
|
-
/// A set is its table, name, flags, key type and key length
|
|
683
|
+
/// A set is its table, name, flags, key type and key length, a map's data type
|
|
684
|
+
/// and length, and the field lengths of a concatenation. The handle, the
|
|
684
685
|
/// informational backend type and count, the transaction ID and an empty
|
|
685
686
|
/// description are not identity; anything else rejects.
|
|
686
687
|
fn set_identity(attributes: &[Attr]) -> Result<Vec<u8>> {
|
|
@@ -688,30 +689,78 @@ fn set_identity(attributes: &[Attr]) -> Result<Vec<u8>> {
|
|
|
688
689
|
for attribute in attributes {
|
|
689
690
|
match attribute.id() {
|
|
690
691
|
10 | 16 | 19 | 20 => {}
|
|
691
|
-
9
|
|
692
|
-
|
|
692
|
+
9 => {
|
|
693
|
+
if let Some(fields) = concatenation(&attribute.value)? {
|
|
694
|
+
identity.push(Attr::bytes(9, fields));
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
1..=7 => identity.push(attribute.clone()),
|
|
693
698
|
_ => return Err(Error::Conflict),
|
|
694
699
|
}
|
|
695
700
|
}
|
|
696
701
|
for id in [1, 2, 3, 4, 5] {
|
|
697
702
|
wire::one(&identity, id)?;
|
|
698
703
|
}
|
|
704
|
+
// A map carries its data type and length, and nothing else does.
|
|
705
|
+
let map = wire::number(&identity, 3)? & 8 != 0;
|
|
706
|
+
for id in [6, 7] {
|
|
707
|
+
if wire::one(&identity, id).is_ok() != map {
|
|
708
|
+
return Err(Error::Conflict);
|
|
709
|
+
}
|
|
710
|
+
}
|
|
699
711
|
serde_json::to_vec(&wire::canonical(&identity, false)?).map_err(|_| Error::Protocol)
|
|
700
712
|
}
|
|
701
|
-
///
|
|
702
|
-
///
|
|
713
|
+
/// The field lengths of a set description: only a concatenation, never a size.
|
|
714
|
+
/// An empty description is none.
|
|
715
|
+
fn concatenation(value: &[u8]) -> Result<Option<Vec<u8>>> {
|
|
716
|
+
let description = wire::attrs(value)?;
|
|
717
|
+
if description.is_empty() {
|
|
718
|
+
return Ok(None);
|
|
719
|
+
}
|
|
720
|
+
if description.len() != 1 || description[0].id() != 2 {
|
|
721
|
+
return Err(Error::Conflict);
|
|
722
|
+
}
|
|
723
|
+
let mut lengths = Vec::new();
|
|
724
|
+
for field in wire::attrs(&description[0].value)? {
|
|
725
|
+
let length = wire::attrs(&field.value)?;
|
|
726
|
+
if field.id() != 1 || length.len() != 1 {
|
|
727
|
+
return Err(Error::Conflict);
|
|
728
|
+
}
|
|
729
|
+
lengths.extend(wire::number(&length, 1)?.to_be_bytes());
|
|
730
|
+
}
|
|
731
|
+
Ok(Some(lengths))
|
|
732
|
+
}
|
|
733
|
+
/// Only compiler elements: a key, the last key of an interval and a map's data.
|
|
734
|
+
/// Flags, timeouts, expressions or any other extension reject. The identity is
|
|
735
|
+
/// the three values, so any changed bound or datum is a changed element.
|
|
703
736
|
fn element_keys(list: &[Attr]) -> Result<Vec<Vec<u8>>> {
|
|
704
737
|
let mut keys = Vec::new();
|
|
705
738
|
for element in list {
|
|
706
|
-
|
|
707
|
-
if element.id() != 1 || fields.len() != 1 {
|
|
739
|
+
if element.id() != 1 {
|
|
708
740
|
return Err(Error::Conflict);
|
|
709
741
|
}
|
|
710
|
-
let
|
|
711
|
-
|
|
712
|
-
|
|
742
|
+
let mut values: [Option<Vec<u8>>; 3] = [None, None, None];
|
|
743
|
+
for field in wire::attrs(&element.value)? {
|
|
744
|
+
let slot = match field.id() {
|
|
745
|
+
1 => 0,
|
|
746
|
+
10 => 1,
|
|
747
|
+
2 => 2,
|
|
748
|
+
_ => return Err(Error::Conflict),
|
|
749
|
+
};
|
|
750
|
+
let value = wire::attrs(&field.value)?;
|
|
751
|
+
if value.len() != 1 || values[slot].is_some() {
|
|
752
|
+
return Err(Error::Conflict);
|
|
753
|
+
}
|
|
754
|
+
values[slot] = Some(wire::one(&value, 1)?.value.clone());
|
|
713
755
|
}
|
|
714
|
-
|
|
756
|
+
let [Some(key), last, data] = values else {
|
|
757
|
+
return Err(Error::Conflict);
|
|
758
|
+
};
|
|
759
|
+
keys.push(if last.is_none() && data.is_none() {
|
|
760
|
+
key
|
|
761
|
+
} else {
|
|
762
|
+
serde_json::to_vec(&(key, last, data)).map_err(|_| Error::Protocol)?
|
|
763
|
+
});
|
|
715
764
|
}
|
|
716
765
|
Ok(keys)
|
|
717
766
|
}
|
|
@@ -741,7 +790,7 @@ fn rule_identity(attributes: &[Attr]) -> Result<(String, Vec<u8>)> {
|
|
|
741
790
|
"cmp" => &[3],
|
|
742
791
|
"bitwise" => &[4, 5],
|
|
743
792
|
"range" => &[3, 4],
|
|
744
|
-
"meta" | "payload" | "ct" | "nat" | "lookup" => &[],
|
|
793
|
+
"meta" | "payload" | "ct" | "nat" | "lookup" | "reject" => &[],
|
|
745
794
|
_ => return Err(Error::Conflict),
|
|
746
795
|
};
|
|
747
796
|
for attribute in &mut data {
|
|
@@ -772,9 +821,10 @@ fn rule_identity(attributes: &[Attr]) -> Result<(String, Vec<u8>)> {
|
|
|
772
821
|
/// neither unknown fields nor default repair may hide a changed kernel rule.
|
|
773
822
|
fn validate_egress_expression(name: &str, data: &[Attr]) -> Result<()> {
|
|
774
823
|
let allowed: &[u16] = match name {
|
|
775
|
-
"lookup" => &[1, 2, 5],
|
|
824
|
+
"lookup" => &[1, 2, 3, 5],
|
|
776
825
|
"ct" | "range" => &[1, 2, 3, 4],
|
|
777
826
|
"nat" => &[1, 2, 3, 4, 5, 6, 7],
|
|
827
|
+
"reject" => &[1],
|
|
778
828
|
_ => return Ok(()),
|
|
779
829
|
};
|
|
780
830
|
for attribute in data {
|
|
@@ -785,10 +835,16 @@ fn validate_egress_expression(name: &str, data: &[Attr]) -> Result<()> {
|
|
|
785
835
|
}
|
|
786
836
|
match name {
|
|
787
837
|
"lookup" => {
|
|
788
|
-
// A
|
|
838
|
+
// A membership or absence test of a named set, or a map lookup into
|
|
839
|
+
// a data register, which is never inverted.
|
|
789
840
|
wire::text(data, 1)?;
|
|
790
841
|
wire::number(data, 2)?;
|
|
791
|
-
|
|
842
|
+
let map = wire::one(data, 3).is_ok();
|
|
843
|
+
if map {
|
|
844
|
+
wire::number(data, 3)?;
|
|
845
|
+
}
|
|
846
|
+
let flags = wire::number(data, 5)?;
|
|
847
|
+
if data.len() != 3 + usize::from(map) || flags > 1 || map && flags != 0 {
|
|
792
848
|
return Err(Error::Conflict);
|
|
793
849
|
}
|
|
794
850
|
}
|
|
@@ -825,6 +881,12 @@ fn validate_egress_expression(name: &str, data: &[Attr]) -> Result<()> {
|
|
|
825
881
|
wire::number(data, 6)?;
|
|
826
882
|
}
|
|
827
883
|
}
|
|
884
|
+
"reject" => {
|
|
885
|
+
// Only a TCP reset, which carries no ICMP code.
|
|
886
|
+
if data.len() != 1 || wire::number(data, 1)? != 1 {
|
|
887
|
+
return Err(Error::Conflict);
|
|
888
|
+
}
|
|
889
|
+
}
|
|
828
890
|
"range" => {
|
|
829
891
|
wire::number(data, 1)?;
|
|
830
892
|
if wire::number(data, 2)? > 1 || data.len() != 4 {
|
|
@@ -389,6 +389,8 @@ fn v2_host_publishes_exact_uplink_ports_and_removes_them_with_the_generation() {
|
|
|
389
389
|
target_port: 8443,
|
|
390
390
|
target_address: "10.240.0.2".into(),
|
|
391
391
|
host_ip: "192.0.2.2".into(),
|
|
392
|
+
host_port_end: None,
|
|
393
|
+
symmetric: false,
|
|
392
394
|
},
|
|
393
395
|
egress::PublishedPort {
|
|
394
396
|
protocol: "udp".into(),
|
|
@@ -396,6 +398,8 @@ fn v2_host_publishes_exact_uplink_ports_and_removes_them_with_the_generation() {
|
|
|
396
398
|
target_port: 8125,
|
|
397
399
|
target_address: "10.240.0.2".into(),
|
|
398
400
|
host_ip: "192.0.2.2".into(),
|
|
401
|
+
host_port_end: None,
|
|
402
|
+
symmetric: false,
|
|
399
403
|
},
|
|
400
404
|
];
|
|
401
405
|
let mut owner = Owner::new(options("host_published")).unwrap();
|
|
@@ -79,13 +79,13 @@ fn guard_policy(revision: u64, granted: bool) -> egress::Policy {
|
|
|
79
79
|
policy
|
|
80
80
|
}
|
|
81
81
|
/// One policy owner in its own namespace; every transition runs there.
|
|
82
|
-
struct Hop {
|
|
82
|
+
pub(super) struct Hop {
|
|
83
83
|
namespace: &'static str,
|
|
84
84
|
owner: Option<Owner>,
|
|
85
85
|
applied: Option<Applied>,
|
|
86
86
|
}
|
|
87
87
|
impl Hop {
|
|
88
|
-
fn new(namespace: &'static str, table: &'static str) -> Self {
|
|
88
|
+
pub(super) fn new(namespace: &'static str, table: &'static str) -> Self {
|
|
89
89
|
let owner = in_namespace(namespace, move || Owner::new(options(table)).unwrap());
|
|
90
90
|
Self {
|
|
91
91
|
namespace,
|
|
@@ -93,7 +93,12 @@ impl Hop {
|
|
|
93
93
|
applied: None,
|
|
94
94
|
}
|
|
95
95
|
}
|
|
96
|
-
fn apply(
|
|
96
|
+
pub(super) fn apply(
|
|
97
|
+
&mut self,
|
|
98
|
+
build: fn(u64, bool) -> egress::Policy,
|
|
99
|
+
revision: u64,
|
|
100
|
+
granted: bool,
|
|
101
|
+
) {
|
|
97
102
|
let mut owner = self.owner.take().unwrap();
|
|
98
103
|
let previous = self.applied.take();
|
|
99
104
|
let (owner, applied) = in_namespace(self.namespace, move || {
|
|
@@ -108,7 +113,7 @@ impl Hop {
|
|
|
108
113
|
self.owner = Some(owner);
|
|
109
114
|
self.applied = Some(applied);
|
|
110
115
|
}
|
|
111
|
-
fn release(mut self) {
|
|
116
|
+
pub(super) fn release(mut self) {
|
|
112
117
|
let mut owner = self.owner.take().unwrap();
|
|
113
118
|
let applied = self.applied.take().unwrap();
|
|
114
119
|
in_namespace(self.namespace, move || {
|