@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
|
@@ -6,15 +6,46 @@ use crate::wire::{self, Attr};
|
|
|
6
6
|
mod host;
|
|
7
7
|
#[path = "egress.hostgrant.rs"]
|
|
8
8
|
mod hostgrant;
|
|
9
|
+
#[path = "egress.keys.rs"]
|
|
10
|
+
mod keys;
|
|
11
|
+
#[path = "egress.leased.rs"]
|
|
12
|
+
mod leased;
|
|
9
13
|
#[path = "egress.poolguard.rs"]
|
|
10
14
|
mod poolguard;
|
|
15
|
+
#[path = "egress.private.rs"]
|
|
16
|
+
mod private;
|
|
17
|
+
#[path = "egress.published.rs"]
|
|
18
|
+
mod published;
|
|
11
19
|
#[path = "egress.router.rs"]
|
|
12
20
|
mod router;
|
|
21
|
+
#[path = "egress.workloadgrant.rs"]
|
|
22
|
+
mod workloadgrant;
|
|
13
23
|
|
|
14
|
-
///
|
|
15
|
-
///
|
|
16
|
-
///
|
|
17
|
-
|
|
24
|
+
/// The atomic replacement budget. One replacement is one nfnetlink batch in one
|
|
25
|
+
/// `sendmsg`, and Linux refuses a netlink message above the socket send buffer:
|
|
26
|
+
/// `SO_SNDBUF` clamped to `net.core.wmem_max` and doubled, 425,984 bytes with the
|
|
27
|
+
/// default `wmem_max` of 212,992. That is the hard ceiling; `wire::BATCH_BYTES`
|
|
28
|
+
/// (320,000) is this package's choice, which fits every `wmem_max` from 160,016
|
|
29
|
+
/// up. A batch carries its begin and end messages (at most 64 bytes), the
|
|
30
|
+
/// handle-only deletion of the previous graph — fewer than `OPERATIONS` rules,
|
|
31
|
+
/// chains and sets, each message at most 140 bytes (the longest table name, a
|
|
32
|
+
/// chain name of up to 32 bytes and a handle), 107,520 bytes — and the complete
|
|
33
|
+
/// target: its rules, chains and sets within `RULE_BYTES`, and together with its
|
|
34
|
+
/// set elements within `TARGET_BYTES`. 64 + 107,520 + 212,000 = 319,584 bytes.
|
|
35
|
+
/// The elements take whatever the rules leave: a set replaces a rule per entry,
|
|
36
|
+
/// so the element share grows exactly where the rule share shrinks.
|
|
37
|
+
pub(super) const OPERATIONS: usize = 768;
|
|
38
|
+
pub(super) const RULE_BYTES: usize = 100_000;
|
|
39
|
+
pub(super) const TARGET_BYTES: usize = 212_000;
|
|
40
|
+
|
|
41
|
+
/// One element of an interval set: the first and last key, each field of the
|
|
42
|
+
/// last at or above the same field of the first, and the data of a map.
|
|
43
|
+
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
|
44
|
+
pub(super) struct IntervalElement {
|
|
45
|
+
pub(super) first: Vec<u8>,
|
|
46
|
+
pub(super) last: Vec<u8>,
|
|
47
|
+
pub(super) data: Option<Vec<u8>>,
|
|
48
|
+
}
|
|
18
49
|
|
|
19
50
|
pub(super) struct Program<'a> {
|
|
20
51
|
table: &'a str,
|
|
@@ -30,10 +61,69 @@ impl<'a> Program<'a> {
|
|
|
30
61
|
// Same atomic replacement reserve as v1: a previous bounded graph must
|
|
31
62
|
// fit its handle-only deletions beside the complete target, and the
|
|
32
63
|
// target's set elements, in one `wire::BATCH_BYTES` batch.
|
|
33
|
-
require(
|
|
64
|
+
require(
|
|
65
|
+
self.bytes <= RULE_BYTES
|
|
66
|
+
&& self.bytes + self.element_bytes <= TARGET_BYTES
|
|
67
|
+
&& self.operations.len() < OPERATIONS,
|
|
68
|
+
)?;
|
|
34
69
|
self.operations.push((operation, attributes));
|
|
35
70
|
Ok(())
|
|
36
71
|
}
|
|
72
|
+
/// Element messages count against the target, beside the rules.
|
|
73
|
+
fn push_elements(&mut self, attributes: Vec<Attr>) -> Result<()> {
|
|
74
|
+
self.element_bytes += wire::encode_attrs(&attributes).len() + 20;
|
|
75
|
+
require(
|
|
76
|
+
self.bytes + self.element_bytes <= TARGET_BYTES && self.operations.len() < OPERATIONS,
|
|
77
|
+
)?;
|
|
78
|
+
self.operations.push((12, attributes));
|
|
79
|
+
Ok(())
|
|
80
|
+
}
|
|
81
|
+
/// One named exact-key hash set, or a map to `data_length` bytes. CONSTANT
|
|
82
|
+
/// like every set here.
|
|
83
|
+
fn exact_set(
|
|
84
|
+
&mut self,
|
|
85
|
+
name: &str,
|
|
86
|
+
id: u32,
|
|
87
|
+
key_length: u32,
|
|
88
|
+
data_length: Option<u32>,
|
|
89
|
+
) -> Result<()> {
|
|
90
|
+
let mut attributes = vec![
|
|
91
|
+
Attr::string(1, self.table),
|
|
92
|
+
Attr::string(2, name),
|
|
93
|
+
Attr::u32(3, 2 | if data_length.is_some() { 8 } else { 0 }),
|
|
94
|
+
Attr::u32(4, 0),
|
|
95
|
+
Attr::u32(5, key_length),
|
|
96
|
+
];
|
|
97
|
+
if let Some(length) = data_length {
|
|
98
|
+
attributes.extend([Attr::u32(6, 0), Attr::u32(7, length)]);
|
|
99
|
+
}
|
|
100
|
+
attributes.push(Attr::u32(10, id));
|
|
101
|
+
self.push(9, attributes)
|
|
102
|
+
}
|
|
103
|
+
fn exact_elements(
|
|
104
|
+
&mut self,
|
|
105
|
+
name: &str,
|
|
106
|
+
elements: Vec<(Vec<u8>, Option<Vec<u8>>)>,
|
|
107
|
+
) -> Result<()> {
|
|
108
|
+
for chunk in elements.chunks(256) {
|
|
109
|
+
let list = chunk
|
|
110
|
+
.iter()
|
|
111
|
+
.map(|(key, data)| {
|
|
112
|
+
let mut fields = vec![Attr::nested(1, vec![Attr::bytes(1, key.clone())])];
|
|
113
|
+
if let Some(data) = data {
|
|
114
|
+
fields.push(Attr::nested(2, vec![Attr::bytes(1, data.clone())]));
|
|
115
|
+
}
|
|
116
|
+
Attr::nested(1, fields)
|
|
117
|
+
})
|
|
118
|
+
.collect();
|
|
119
|
+
self.push_elements(vec![
|
|
120
|
+
Attr::string(1, self.table),
|
|
121
|
+
Attr::string(2, name),
|
|
122
|
+
Attr::nested(3, list),
|
|
123
|
+
])?;
|
|
124
|
+
}
|
|
125
|
+
Ok(())
|
|
126
|
+
}
|
|
37
127
|
/// One named exact-match set. CONSTANT: its elements cannot change once a
|
|
38
128
|
/// rule binds it, so replacement always creates a complete new set.
|
|
39
129
|
fn set(&mut self, name: &str, id: u32, key_length: usize) -> Result<()> {
|
|
@@ -54,21 +144,72 @@ impl<'a> Program<'a> {
|
|
|
54
144
|
/// own budget: a replacement deletes the previous set, and so its elements,
|
|
55
145
|
/// by one handle.
|
|
56
146
|
fn elements(&mut self, name: &str, keys: Vec<Vec<u8>>) -> Result<()> {
|
|
57
|
-
|
|
58
|
-
|
|
147
|
+
self.exact_elements(name, keys.into_iter().map(|key| (key, None)).collect())
|
|
148
|
+
}
|
|
149
|
+
/// One named CONSTANT set of concatenated fields in which every field is an
|
|
150
|
+
/// inclusive interval (the kernel's pipapo backend), optionally a map to
|
|
151
|
+
/// `data_length` bytes. `fields` are byte lengths; each occupies whole
|
|
152
|
+
/// 32-bit registers of the key.
|
|
153
|
+
fn interval_set(
|
|
154
|
+
&mut self,
|
|
155
|
+
name: &str,
|
|
156
|
+
id: u32,
|
|
157
|
+
fields: &[u32],
|
|
158
|
+
data_length: Option<u32>,
|
|
159
|
+
) -> Result<()> {
|
|
160
|
+
let key_length: u32 = fields.iter().map(|length| length.div_ceil(4) * 4).sum();
|
|
161
|
+
let mut attributes = vec![
|
|
162
|
+
Attr::string(1, self.table),
|
|
163
|
+
Attr::string(2, name),
|
|
164
|
+
// CONSTANT | INTERVAL | CONCAT, and MAP for a map.
|
|
165
|
+
Attr::u32(3, 2 | 4 | 0x80 | if data_length.is_some() { 8 } else { 0 }),
|
|
166
|
+
Attr::u32(4, 0),
|
|
167
|
+
Attr::u32(5, key_length),
|
|
168
|
+
];
|
|
169
|
+
if let Some(length) = data_length {
|
|
170
|
+
attributes.extend([Attr::u32(6, 0), Attr::u32(7, length)]);
|
|
171
|
+
}
|
|
172
|
+
attributes.extend([
|
|
173
|
+
Attr::nested(
|
|
174
|
+
9,
|
|
175
|
+
vec![Attr::nested(
|
|
176
|
+
2,
|
|
177
|
+
fields
|
|
178
|
+
.iter()
|
|
179
|
+
.map(|length| Attr::nested(1, vec![Attr::u32(1, *length)]))
|
|
180
|
+
.collect(),
|
|
181
|
+
)],
|
|
182
|
+
),
|
|
183
|
+
Attr::u32(10, id),
|
|
184
|
+
]);
|
|
185
|
+
self.push(9, attributes)
|
|
186
|
+
}
|
|
187
|
+
/// Interval elements `(first, last, data)`, 256 to a message like `elements`.
|
|
188
|
+
/// An element whose bounds are equal carries no last key; Linux takes the
|
|
189
|
+
/// first as the last and dumps it the same way.
|
|
190
|
+
fn interval_elements(&mut self, name: &str, elements: Vec<IntervalElement>) -> Result<()> {
|
|
191
|
+
for chunk in elements.chunks(256) {
|
|
192
|
+
let list = chunk
|
|
59
193
|
.iter()
|
|
60
|
-
.map(|
|
|
61
|
-
|
|
194
|
+
.map(|element| {
|
|
195
|
+
let value = |kind: u16, bytes: &[u8]| {
|
|
196
|
+
Attr::nested(kind, vec![Attr::bytes(1, bytes.to_vec())])
|
|
197
|
+
};
|
|
198
|
+
let mut fields = vec![value(1, &element.first)];
|
|
199
|
+
if element.last != element.first {
|
|
200
|
+
fields.push(value(10, &element.last));
|
|
201
|
+
}
|
|
202
|
+
if let Some(data) = &element.data {
|
|
203
|
+
fields.push(value(2, data));
|
|
204
|
+
}
|
|
205
|
+
Attr::nested(1, fields)
|
|
62
206
|
})
|
|
63
207
|
.collect();
|
|
64
|
-
|
|
208
|
+
self.push_elements(vec![
|
|
65
209
|
Attr::string(1, self.table),
|
|
66
210
|
Attr::string(2, name),
|
|
67
|
-
Attr::nested(3,
|
|
68
|
-
]
|
|
69
|
-
self.element_bytes += wire::encode_attrs(&attributes).len() + 20;
|
|
70
|
-
require(self.element_bytes <= ELEMENT_BYTES && self.operations.len() < 768)?;
|
|
71
|
-
self.operations.push((12, attributes));
|
|
211
|
+
Attr::nested(3, list),
|
|
212
|
+
])?;
|
|
72
213
|
}
|
|
73
214
|
Ok(())
|
|
74
215
|
}
|
|
@@ -241,12 +382,6 @@ fn immediate(register: u32, value: Vec<u8>) -> Attr {
|
|
|
241
382
|
],
|
|
242
383
|
)
|
|
243
384
|
}
|
|
244
|
-
fn set_ct(key: u32, value: Vec<u8>) -> Vec<Attr> {
|
|
245
|
-
vec![
|
|
246
|
-
immediate(1, value),
|
|
247
|
-
expr("ct", vec![Attr::u32(2, key), Attr::u32(4, 1)]),
|
|
248
|
-
]
|
|
249
|
-
}
|
|
250
385
|
fn label(generation: &Generation) -> Vec<u8> {
|
|
251
386
|
// A label is an opaque canonical 16-byte value, never a host-endian integer.
|
|
252
387
|
generation
|
|
@@ -256,14 +391,6 @@ fn label(generation: &Generation) -> Vec<u8> {
|
|
|
256
391
|
.map(|pair| u8::from_str_radix(std::str::from_utf8(pair).unwrap(), 16).unwrap())
|
|
257
392
|
.collect()
|
|
258
393
|
}
|
|
259
|
-
fn generation(generation: &Generation, direction: u8, labelled: bool) -> Vec<Attr> {
|
|
260
|
-
let mut result = ct(17, None, generation.conntrack_zone.to_ne_bytes().to_vec());
|
|
261
|
-
result.extend(ct(1, None, vec![direction]));
|
|
262
|
-
if labelled {
|
|
263
|
-
result.extend(ct(13, None, label(generation)));
|
|
264
|
-
}
|
|
265
|
-
result
|
|
266
|
-
}
|
|
267
394
|
fn state(value: u32) -> Vec<Attr> {
|
|
268
395
|
ct(0, None, value.to_ne_bytes().to_vec())
|
|
269
396
|
}
|
|
@@ -295,50 +422,6 @@ fn snat(address: &str, range: Option<(u16, u16)>) -> Result<Vec<Attr>> {
|
|
|
295
422
|
result.push(expr("nat", data));
|
|
296
423
|
Ok(result)
|
|
297
424
|
}
|
|
298
|
-
/// Published inbound translation. Address and port are always both specified, so
|
|
299
|
-
/// the kernel never picks a port and the graph identity carries explicit registers.
|
|
300
|
-
fn dnat(address: &str, port: u16) -> Result<Vec<Attr>> {
|
|
301
|
-
Ok(vec![
|
|
302
|
-
immediate(1, super::address(address)?.to_be_bytes().to_vec()),
|
|
303
|
-
immediate(2, port.to_be_bytes().to_vec()),
|
|
304
|
-
expr(
|
|
305
|
-
"nat",
|
|
306
|
-
vec![
|
|
307
|
-
Attr::u32(1, 1),
|
|
308
|
-
Attr::u32(2, 2),
|
|
309
|
-
Attr::u32(3, 1),
|
|
310
|
-
Attr::u32(4, 1),
|
|
311
|
-
Attr::u32(5, 2),
|
|
312
|
-
Attr::u32(6, 2),
|
|
313
|
-
Attr::u32(7, 3),
|
|
314
|
-
],
|
|
315
|
-
),
|
|
316
|
-
])
|
|
317
|
-
}
|
|
318
|
-
fn tuple(grant: &Grant, protection: &Protection, reply: bool, original: bool) -> Result<Vec<Attr>> {
|
|
319
|
-
let destination = super::destination(grant, protection)?;
|
|
320
|
-
let mut result = ipv4();
|
|
321
|
-
result.extend(meta(16, vec![protocol(&grant.protocol)]));
|
|
322
|
-
result.extend(current_address(&grant.source_prefix, !reply)?);
|
|
323
|
-
result.extend(current_address(&destination, reply)?);
|
|
324
|
-
for (port, source) in [(grant.source_port, !reply), (grant.destination_port, reply)] {
|
|
325
|
-
if let Some(port) = port {
|
|
326
|
-
result.extend(current_port(port, source));
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
if original {
|
|
330
|
-
result.extend(original_address(&grant.source_prefix, true)?);
|
|
331
|
-
result.extend(original_address(&destination, false)?);
|
|
332
|
-
result.extend(ct(10, None, vec![protocol(&grant.protocol)]));
|
|
333
|
-
for (port, source) in [(grant.source_port, true), (grant.destination_port, false)] {
|
|
334
|
-
if let Some(port) = port {
|
|
335
|
-
result.extend(original_port(port, source));
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
Ok(result)
|
|
340
|
-
}
|
|
341
|
-
|
|
342
425
|
pub(super) fn compile(prepared: &Prepared, table: &str) -> Result<Vec<(u16, Vec<Attr>)>> {
|
|
343
426
|
let mut program = Program {
|
|
344
427
|
table,
|
|
@@ -349,9 +432,7 @@ pub(super) fn compile(prepared: &Prepared, table: &str) -> Result<Vec<(u16, Vec<
|
|
|
349
432
|
operations: Vec::new(),
|
|
350
433
|
};
|
|
351
434
|
match &prepared.policy.scope {
|
|
352
|
-
Scope::RouterEgress(scope) =>
|
|
353
|
-
router::compile(&mut program, scope, prepared.policy.revision)?
|
|
354
|
-
}
|
|
435
|
+
Scope::RouterEgress(scope) => router::compile(&mut program, scope)?,
|
|
355
436
|
Scope::HostTransit(scope) => host::compile(&mut program, scope)?,
|
|
356
437
|
Scope::AllocationPoolGuard(scope) => poolguard::compile(&mut program, scope)?,
|
|
357
438
|
}
|
package/rust/src/egress.host.rs
CHANGED
|
@@ -36,10 +36,25 @@ fn envelope(
|
|
|
36
36
|
allocation: &Allocation,
|
|
37
37
|
range: &PortRange,
|
|
38
38
|
reply: bool,
|
|
39
|
+
) -> Result<Vec<Attr>> {
|
|
40
|
+
leased(handoff, Some(&scope.uplink), allocation, range, reply)
|
|
41
|
+
}
|
|
42
|
+
/// One leased range of one allocation on its exact handoff: the default zone, the
|
|
43
|
+
/// direction, the protocols, and the transit source address and port range in
|
|
44
|
+
/// both the live and the originally tracked tuple. A forwarded envelope adds the
|
|
45
|
+
/// uplink; a host-local one has none.
|
|
46
|
+
fn leased(
|
|
47
|
+
handoff: &HostHandoff,
|
|
48
|
+
uplink: Option<&LocalLink>,
|
|
49
|
+
allocation: &Allocation,
|
|
50
|
+
range: &PortRange,
|
|
51
|
+
reply: bool,
|
|
39
52
|
) -> Result<Vec<Attr>> {
|
|
40
53
|
let mut expressions = ipv4();
|
|
41
54
|
expressions.extend(link(&handoff.link, !reply));
|
|
42
|
-
|
|
55
|
+
if let Some(uplink) = uplink {
|
|
56
|
+
expressions.extend(link(uplink, reply));
|
|
57
|
+
}
|
|
43
58
|
expressions.extend(meta(16, vec![protocol(&range.protocol)]));
|
|
44
59
|
expressions.extend(ct(10, None, vec![protocol(&range.protocol)]));
|
|
45
60
|
expressions.extend(ct(17, None, 0_u16.to_ne_bytes().to_vec()));
|
|
@@ -51,34 +66,12 @@ fn envelope(
|
|
|
51
66
|
expressions.extend(original_range(range.first, range.last));
|
|
52
67
|
Ok(expressions)
|
|
53
68
|
}
|
|
54
|
-
///
|
|
55
|
-
///
|
|
56
|
-
///
|
|
57
|
-
fn
|
|
58
|
-
|
|
59
|
-
handoff: &HostHandoff,
|
|
60
|
-
port: &PublishedPort,
|
|
61
|
-
reply: bool,
|
|
62
|
-
) -> Result<Vec<Attr>> {
|
|
63
|
-
let mut expressions = ipv4();
|
|
64
|
-
expressions.extend(link(&scope.uplink, !reply));
|
|
65
|
-
expressions.extend(link(&handoff.link, reply));
|
|
66
|
-
expressions.extend(meta(16, vec![protocol(&port.protocol)]));
|
|
67
|
-
expressions.extend(ct(10, None, vec![protocol(&port.protocol)]));
|
|
68
|
-
expressions.extend(ct(17, None, 0_u16.to_ne_bytes().to_vec()));
|
|
69
|
-
expressions.extend(ct(1, None, vec![u8::from(reply)]));
|
|
70
|
-
let target = format!("{}/32", port.target_address);
|
|
71
|
-
expressions.extend(current_address(&target, reply)?);
|
|
72
|
-
expressions.extend(current_port(port.target_port, reply));
|
|
73
|
-
let published = format!("{}/32", port.host_ip);
|
|
74
|
-
expressions.extend(original_address(&published, false)?);
|
|
75
|
-
expressions.extend(original_port(port.host_port, false));
|
|
76
|
-
Ok(expressions)
|
|
77
|
-
}
|
|
78
|
-
fn published(program: &mut Program<'_>, scope: &HostScope) -> Result<()> {
|
|
69
|
+
/// The publications of this hop. Validation admits only leased transit source
|
|
70
|
+
/// addresses as targets, and one address never spans two active handoff links,
|
|
71
|
+
/// so each publication has exactly one handoff.
|
|
72
|
+
fn hop(scope: &HostScope) -> Result<published::Hop<'_>> {
|
|
73
|
+
let mut publications = Vec::new();
|
|
79
74
|
for port in &scope.published_ports {
|
|
80
|
-
// Validation admits only leased transit source addresses, and one address
|
|
81
|
-
// never spans two active handoff links.
|
|
82
75
|
let handoff = scope
|
|
83
76
|
.handoffs
|
|
84
77
|
.iter()
|
|
@@ -89,28 +82,80 @@ fn published(program: &mut Program<'_>, scope: &HostScope) -> Result<()> {
|
|
|
89
82
|
.any(|allocation| allocation.transit_source_address == port.target_address)
|
|
90
83
|
})
|
|
91
84
|
.ok_or(crate::Error::Invalid)?;
|
|
92
|
-
|
|
85
|
+
publications.push(published::Publication {
|
|
86
|
+
protocol: &port.protocol,
|
|
87
|
+
outside: (&port.host_ip, port.host_port, port.host_last()),
|
|
88
|
+
inside: (&port.target_address, port.target_port, port.target_last()),
|
|
89
|
+
link: &handoff.link,
|
|
90
|
+
symmetric: port.symmetric,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
Ok(published::Hop {
|
|
94
|
+
publications,
|
|
95
|
+
fixed: &scope.uplink,
|
|
96
|
+
})
|
|
97
|
+
}
|
|
98
|
+
/// The exact platform endpoints served on host addresses. Every packet is
|
|
99
|
+
/// checked against the live and the originally tracked destination tuple, so a
|
|
100
|
+
/// flow rewritten in transit never matches.
|
|
101
|
+
fn local_endpoints(scope: &HostScope) -> Vec<&PlatformEndpoint> {
|
|
102
|
+
scope
|
|
103
|
+
.protection
|
|
104
|
+
.platform_endpoints
|
|
105
|
+
.iter()
|
|
106
|
+
.filter(|endpoint| scope.local_platform_endpoints.contains(&endpoint.id))
|
|
107
|
+
.collect()
|
|
108
|
+
}
|
|
109
|
+
fn local_endpoint(endpoint: &PlatformEndpoint, reply: bool) -> Result<Vec<Attr>> {
|
|
110
|
+
let mut expressions = meta(16, vec![protocol(&endpoint.protocol)]);
|
|
111
|
+
expressions.extend(ct(10, None, vec![protocol(&endpoint.protocol)]));
|
|
112
|
+
let address = format!("{}/32", endpoint.address);
|
|
113
|
+
expressions.extend(current_address(&address, reply)?);
|
|
114
|
+
expressions.extend(current_port(endpoint.port, reply));
|
|
115
|
+
expressions.extend(original_address(&address, false)?);
|
|
116
|
+
expressions.extend(original_port(endpoint.port, false));
|
|
117
|
+
Ok(expressions)
|
|
118
|
+
}
|
|
119
|
+
/// Leased flows into host-local platform endpoints, the reverse of a host grant:
|
|
120
|
+
/// INPUT admits the original direction only from an exact handoff with a leased
|
|
121
|
+
/// source address and port range of that handoff, NEW (a TCP opening only with
|
|
122
|
+
/// SYN) or ESTABLISHED; OUTPUT admits only the ESTABLISHED reply back to it. Each
|
|
123
|
+
/// leased range jumps once per direction, and the endpoint chains hold one tuple
|
|
124
|
+
/// check per endpoint, so the graph grows with ranges plus endpoints.
|
|
125
|
+
fn local_platform(program: &mut Program<'_>, scope: &HostScope) -> Result<()> {
|
|
126
|
+
let endpoints = local_endpoints(scope);
|
|
127
|
+
for endpoint in &endpoints {
|
|
128
|
+
let original = local_endpoint(endpoint, false)?;
|
|
93
129
|
for connection_state in [2, 8] {
|
|
94
130
|
let mut expressions = original.clone();
|
|
95
131
|
expressions.extend(state(connection_state));
|
|
96
|
-
if connection_state == 8 &&
|
|
132
|
+
if connection_state == 8 && endpoint.protocol == "tcp" {
|
|
97
133
|
expressions.extend(opening_tcp());
|
|
98
134
|
}
|
|
99
|
-
program.end("
|
|
135
|
+
program.end("local_platform_in", expressions, 1)?;
|
|
100
136
|
}
|
|
101
|
-
let mut reply =
|
|
137
|
+
let mut reply = local_endpoint(endpoint, true)?;
|
|
102
138
|
reply.extend(state(2));
|
|
103
|
-
program.end("
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
139
|
+
program.end("local_platform_back", reply, 1)?;
|
|
140
|
+
}
|
|
141
|
+
for handoff in &scope.handoffs {
|
|
142
|
+
for allocation in &handoff.allocations {
|
|
143
|
+
for range in &allocation.source_port_ranges {
|
|
144
|
+
if endpoints
|
|
145
|
+
.iter()
|
|
146
|
+
.all(|endpoint| endpoint.protocol != range.protocol)
|
|
147
|
+
{
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
for (chain, target, reply) in [
|
|
151
|
+
("input", "local_platform_in", false),
|
|
152
|
+
("output", "local_platform_back", true),
|
|
153
|
+
] {
|
|
154
|
+
let expressions = leased(handoff, None, allocation, range, reply)?;
|
|
155
|
+
program.jump(chain, expressions, target)?;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
114
159
|
}
|
|
115
160
|
Ok(())
|
|
116
161
|
}
|
|
@@ -135,6 +180,11 @@ pub(super) fn compile(program: &mut Program<'_>, scope: &HostScope) -> Result<()
|
|
|
135
180
|
for name in ["protected_out", "protected_back"] {
|
|
136
181
|
program.chain(name, None)?;
|
|
137
182
|
}
|
|
183
|
+
if !scope.local_platform_endpoints.is_empty() {
|
|
184
|
+
for name in ["local_platform_in", "local_platform_back"] {
|
|
185
|
+
program.chain(name, None)?;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
138
188
|
if !scope.host_grants.is_empty() {
|
|
139
189
|
// Validation admits only an address of exactly one handoff link.
|
|
140
190
|
hostgrant::tuple_set(program, &scope.host_grants, |grant| {
|
|
@@ -151,18 +201,24 @@ pub(super) fn compile(program: &mut Program<'_>, scope: &HostScope) -> Result<()
|
|
|
151
201
|
.ok_or(crate::Error::Invalid)
|
|
152
202
|
})?;
|
|
153
203
|
}
|
|
204
|
+
let hop = hop(scope)?;
|
|
205
|
+
published::sets(program, &hop, false)?;
|
|
154
206
|
protection(program, scope, false)?;
|
|
155
207
|
protection(program, scope, true)?;
|
|
208
|
+
published::translations(program, &hop)?;
|
|
156
209
|
// Published admission precedes the capture barrier: the barrier denies every
|
|
157
210
|
// protected original destination, which includes this uplink address, and an
|
|
158
211
|
// exact inbound publication is the explicit authority for that exception.
|
|
159
|
-
published(program,
|
|
212
|
+
published::inbound(program, &hop)?;
|
|
160
213
|
// Host-origin grants leave through the exact handoff that holds their source
|
|
161
214
|
// address, which is part of every set key, and precede the terminal output
|
|
162
215
|
// and input denials. Nothing else of the host's own traffic crosses a handoff.
|
|
163
216
|
if !scope.host_grants.is_empty() {
|
|
164
217
|
hostgrant::admissions(program, ["output", "input"], true, |_| vec![])?;
|
|
165
218
|
}
|
|
219
|
+
// Leased flows into host-local platform endpoints precede the same terminal
|
|
220
|
+
// handoff denials, which remain for everything else.
|
|
221
|
+
local_platform(program, scope)?;
|
|
166
222
|
for handoff in &scope.handoffs {
|
|
167
223
|
program.jump("forward", link(&handoff.link, true), "protected_out")?;
|
|
168
224
|
program.jump("forward", link(&handoff.link, false), "protected_back")?;
|
|
@@ -188,6 +244,10 @@ pub(super) fn compile(program: &mut Program<'_>, scope: &HostScope) -> Result<()
|
|
|
188
244
|
}
|
|
189
245
|
}
|
|
190
246
|
}
|
|
247
|
+
// Symmetric flows leave from ports outside every leased range of their
|
|
248
|
+
// address, after the capture barrier and before the terminal handoff denials.
|
|
249
|
+
published::symmetric(program, &hop)?;
|
|
250
|
+
published::symmetric_translation(program, &hop)?;
|
|
191
251
|
for handoff in &scope.handoffs {
|
|
192
252
|
program.deny_link("input", &handoff.link, true)?;
|
|
193
253
|
program.deny_link("output", &handoff.link, false)?;
|
|
@@ -13,7 +13,7 @@ pub(super) const ARRIVAL_SET: &str = "host_grant_arrival";
|
|
|
13
13
|
|
|
14
14
|
/// Register numbers as Linux dumps them: 16-byte aligned registers use the
|
|
15
15
|
/// legacy NFT_REG_1..4 names, every other 4-byte register NFT_REG32_xx.
|
|
16
|
-
fn register(index: u32) -> u32 {
|
|
16
|
+
pub(super) fn register(index: u32) -> u32 {
|
|
17
17
|
if index % 4 == 0 {
|
|
18
18
|
index / 4
|
|
19
19
|
} else {
|
|
@@ -24,10 +24,10 @@ fn store(name: &str, mut data: Vec<Attr>, index: u32) -> Attr {
|
|
|
24
24
|
data.insert(0, Attr::u32(1, register(index)));
|
|
25
25
|
expr(name, data)
|
|
26
26
|
}
|
|
27
|
-
fn meta_to(key: u32, index: u32) -> Attr {
|
|
27
|
+
pub(super) fn meta_to(key: u32, index: u32) -> Attr {
|
|
28
28
|
store("meta", vec![Attr::u32(2, key)], index)
|
|
29
29
|
}
|
|
30
|
-
fn payload_to(base: u32, offset: u32, length: u32, index: u32) -> Attr {
|
|
30
|
+
pub(super) fn payload_to(base: u32, offset: u32, length: u32, index: u32) -> Attr {
|
|
31
31
|
store(
|
|
32
32
|
"payload",
|
|
33
33
|
vec![
|
|
@@ -38,14 +38,14 @@ fn payload_to(base: u32, offset: u32, length: u32, index: u32) -> Attr {
|
|
|
38
38
|
index,
|
|
39
39
|
)
|
|
40
40
|
}
|
|
41
|
-
fn ct_to(key: u32, original: bool, index: u32) -> Attr {
|
|
41
|
+
pub(super) fn ct_to(key: u32, original: bool, index: u32) -> Attr {
|
|
42
42
|
let mut data = vec![Attr::u32(2, key)];
|
|
43
43
|
if original {
|
|
44
44
|
data.push(Attr::bytes(3, vec![0]));
|
|
45
45
|
}
|
|
46
46
|
store("ct", data, index)
|
|
47
47
|
}
|
|
48
|
-
fn lookup(set: &str) -> Attr {
|
|
48
|
+
pub(super) fn lookup(set: &str) -> Attr {
|
|
49
49
|
expr(
|
|
50
50
|
"lookup",
|
|
51
51
|
vec![
|
|
@@ -61,10 +61,10 @@ fn word(bytes: &[u8]) -> Vec<u8> {
|
|
|
61
61
|
result
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
/// Key loads for one direction, then the lookup
|
|
65
|
-
/// interface: the outgoing interface of the original direction, the
|
|
66
|
-
/// interface of the reply.
|
|
67
|
-
fn tuple_lookup(linked: bool, reply: bool) -> Vec<Attr> {
|
|
64
|
+
/// Key loads for one direction, then the lookup in `set`. `linked` adds the
|
|
65
|
+
/// variable interface: the outgoing interface of the original direction, the
|
|
66
|
+
/// incoming interface of the reply.
|
|
67
|
+
pub(super) fn tuple_lookup(set: &str, linked: bool, reply: bool) -> Vec<Attr> {
|
|
68
68
|
let mut result = Vec::new();
|
|
69
69
|
let mut index = 4;
|
|
70
70
|
if linked {
|
|
@@ -82,10 +82,10 @@ fn tuple_lookup(linked: bool, reply: bool) -> Vec<Attr> {
|
|
|
82
82
|
result.push(ct_to(19, true, index + 5));
|
|
83
83
|
result.push(ct_to(20, true, index + 6));
|
|
84
84
|
result.push(ct_to(12, true, index + 7));
|
|
85
|
-
result.push(lookup(
|
|
85
|
+
result.push(lookup(set));
|
|
86
86
|
result
|
|
87
87
|
}
|
|
88
|
-
fn tuple_key(grant: &HostGrant, link: Option<&LocalLink>) -> Result<Vec<u8>> {
|
|
88
|
+
pub(super) fn tuple_key(grant: &HostGrant, link: Option<&LocalLink>) -> Result<Vec<u8>> {
|
|
89
89
|
let protocol = word(&[protocol(&grant.protocol)]);
|
|
90
90
|
let source = address(&grant.source_address)?.to_be_bytes();
|
|
91
91
|
let destination = address(&grant.destination_address)?.to_be_bytes();
|
|
@@ -170,7 +170,7 @@ pub(super) fn admissions(
|
|
|
170
170
|
result
|
|
171
171
|
};
|
|
172
172
|
let mut established = envelope(false, 2);
|
|
173
|
-
established.extend(tuple_lookup(linked, false));
|
|
173
|
+
established.extend(tuple_lookup(TUPLE_SET, linked, false));
|
|
174
174
|
program.end(chains[0], established, 1)?;
|
|
175
175
|
for transport in ["udp", "tcp"] {
|
|
176
176
|
let mut opening = envelope(false, 8);
|
|
@@ -178,10 +178,10 @@ pub(super) fn admissions(
|
|
|
178
178
|
if transport == "tcp" {
|
|
179
179
|
opening.extend(opening_tcp());
|
|
180
180
|
}
|
|
181
|
-
opening.extend(tuple_lookup(linked, false));
|
|
181
|
+
opening.extend(tuple_lookup(TUPLE_SET, linked, false));
|
|
182
182
|
program.end(chains[0], opening, 1)?;
|
|
183
183
|
}
|
|
184
184
|
let mut reply = envelope(true, 2);
|
|
185
|
-
reply.extend(tuple_lookup(linked, true));
|
|
185
|
+
reply.extend(tuple_lookup(TUPLE_SET, linked, true));
|
|
186
186
|
program.end(chains[1], reply, 1)
|
|
187
187
|
}
|