@push.rocks/smartnftables 2.6.0 → 4.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 +33 -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/classes.manageddockerforwarding.js +3 -4
- package/dist_ts/classes.managednftables.d.ts +14 -2
- package/dist_ts/classes.managednftables.js +48 -8
- package/dist_ts/managed.egress.types.d.ts +25 -3
- package/dist_ts/managed.types.d.ts +14 -0
- package/package.json +7 -7
- package/readme.md +262 -64
- package/rust/src/docker.policy.rs +2 -1
- package/rust/src/egress.compile.rs +153 -76
- package/rust/src/egress.host.rs +24 -51
- package/rust/src/egress.hostgrant.rs +1 -1
- package/rust/src/egress.keys.rs +305 -0
- package/rust/src/egress.leased.rs +399 -0
- package/rust/src/egress.private.rs +197 -0
- package/rust/src/egress.published.rs +370 -0
- package/rust/src/egress.router.rs +61 -294
- package/rust/src/egress.rs +140 -19
- package/rust/src/egress_hostgrant_tests.rs +158 -20
- package/rust/src/egress_localport_tests.rs +11 -2
- package/rust/src/egress_publishedrange_tests.rs +812 -0
- package/rust/src/egress_routerequivalence_tests.rs +448 -0
- package/rust/src/egress_tests.rs +289 -230
- package/rust/src/egress_workloadgrant_tests.rs +21 -13
- package/rust/src/main.rs +57 -3
- package/rust/src/owner.rs +69 -14
- package/rust/src/owner_host_traffic_tests.rs +4 -0
- package/rust/src/owner_identity_tests.rs +166 -80
- 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 +6 -0
- package/rust/src/policy.rs +30 -29
- package/rust/src/tests.rs +100 -0
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.manageddockerforwarding.ts +2 -3
- package/ts/classes.managednftables.ts +46 -8
- package/ts/managed.egress.types.ts +25 -3
- package/ts/managed.types.ts +17 -0
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
//! Concatenated set keys shared by every set-backed part of the compiled graph:
|
|
2
|
+
//! the loads that build a key in consecutive 32-bit registers, element bounds
|
|
3
|
+
//! built field by field, the disjoint union the kernel's interval backend needs,
|
|
4
|
+
//! and set declarations that are never created empty.
|
|
5
|
+
use super::hostgrant::{ct_to, lookup, meta_to, payload_to, register};
|
|
6
|
+
use super::*;
|
|
7
|
+
|
|
8
|
+
/// Field byte lengths. Each field occupies whole 32-bit registers of the key.
|
|
9
|
+
pub(super) const INDEX: u32 = 4;
|
|
10
|
+
pub(super) const NAME: u32 = 16;
|
|
11
|
+
pub(super) const PROTOCOL: u32 = 1;
|
|
12
|
+
pub(super) const ADDRESS: u32 = 4;
|
|
13
|
+
pub(super) const PORT: u32 = 2;
|
|
14
|
+
pub(super) const ZONE: u32 = 2;
|
|
15
|
+
pub(super) const LABEL: u32 = 16;
|
|
16
|
+
|
|
17
|
+
/// Key loads into consecutive 32-bit registers, starting where a lookup reads.
|
|
18
|
+
pub(super) struct Loads {
|
|
19
|
+
expressions: Vec<Attr>,
|
|
20
|
+
index: u32,
|
|
21
|
+
}
|
|
22
|
+
impl Loads {
|
|
23
|
+
pub(super) fn new() -> Self {
|
|
24
|
+
Self {
|
|
25
|
+
expressions: Vec::new(),
|
|
26
|
+
index: 4,
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
pub(super) fn push(&mut self, load: impl FnOnce(u32) -> Attr, length: u32) -> &mut Self {
|
|
30
|
+
self.expressions.push(load(self.index));
|
|
31
|
+
self.index += length.div_ceil(4);
|
|
32
|
+
self
|
|
33
|
+
}
|
|
34
|
+
/// An interface index; an absent interface loads as index 0.
|
|
35
|
+
pub(super) fn index(&mut self, incoming: bool) -> &mut Self {
|
|
36
|
+
self.push(|at| meta_to(if incoming { 4 } else { 5 }, at), INDEX)
|
|
37
|
+
}
|
|
38
|
+
/// An interface index and name; an absent interface loads as 0 and "".
|
|
39
|
+
pub(super) fn link(&mut self, incoming: bool) -> &mut Self {
|
|
40
|
+
self.index(incoming)
|
|
41
|
+
.push(|at| meta_to(if incoming { 6 } else { 7 }, at), NAME)
|
|
42
|
+
}
|
|
43
|
+
pub(super) fn protocol(&mut self) -> &mut Self {
|
|
44
|
+
self.push(|at| meta_to(16, at), PROTOCOL)
|
|
45
|
+
}
|
|
46
|
+
pub(super) fn protocols(&mut self) -> &mut Self {
|
|
47
|
+
self.protocol().push(|at| ct_to(10, false, at), PROTOCOL)
|
|
48
|
+
}
|
|
49
|
+
pub(super) fn address(&mut self, source: bool) -> &mut Self {
|
|
50
|
+
self.push(
|
|
51
|
+
|at| payload_to(1, if source { 12 } else { 16 }, 4, at),
|
|
52
|
+
ADDRESS,
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
pub(super) fn port(&mut self, source: bool) -> &mut Self {
|
|
56
|
+
self.push(|at| payload_to(2, if source { 0 } else { 2 }, 2, at), PORT)
|
|
57
|
+
}
|
|
58
|
+
/// The originally tracked address and port of one side.
|
|
59
|
+
pub(super) fn original(&mut self, source: bool) -> &mut Self {
|
|
60
|
+
self.original_address(source).original_port(source)
|
|
61
|
+
}
|
|
62
|
+
pub(super) fn original_address(&mut self, source: bool) -> &mut Self {
|
|
63
|
+
self.push(|at| ct_to(if source { 19 } else { 20 }, true, at), ADDRESS)
|
|
64
|
+
}
|
|
65
|
+
pub(super) fn original_port(&mut self, source: bool) -> &mut Self {
|
|
66
|
+
self.push(|at| ct_to(if source { 11 } else { 12 }, true, at), PORT)
|
|
67
|
+
}
|
|
68
|
+
pub(super) fn zone(&mut self) -> &mut Self {
|
|
69
|
+
self.push(|at| ct_to(17, false, at), ZONE)
|
|
70
|
+
}
|
|
71
|
+
pub(super) fn label(&mut self) -> &mut Self {
|
|
72
|
+
self.push(|at| ct_to(13, false, at), LABEL)
|
|
73
|
+
}
|
|
74
|
+
fn finish(&mut self, set: &str, data: bool, invert: bool) -> Vec<Attr> {
|
|
75
|
+
let mut result = std::mem::take(&mut self.expressions);
|
|
76
|
+
self.index = 4;
|
|
77
|
+
let mut expression = lookup(set);
|
|
78
|
+
if data || invert {
|
|
79
|
+
let mut fields = vec![Attr::string(1, set), Attr::u32(2, register(4))];
|
|
80
|
+
if data {
|
|
81
|
+
// The map's data replaces the key from the first register on.
|
|
82
|
+
fields.push(Attr::u32(3, register(4)));
|
|
83
|
+
}
|
|
84
|
+
fields.push(Attr::u32(5, u32::from(invert)));
|
|
85
|
+
expression = expr("lookup", fields);
|
|
86
|
+
}
|
|
87
|
+
result.push(expression);
|
|
88
|
+
result
|
|
89
|
+
}
|
|
90
|
+
/// Membership of the loaded key.
|
|
91
|
+
pub(super) fn lookup(&mut self, set: &str) -> Vec<Attr> {
|
|
92
|
+
self.finish(set, false, false)
|
|
93
|
+
}
|
|
94
|
+
/// A map lookup storing the element's data from the first key register on.
|
|
95
|
+
pub(super) fn map(&mut self, set: &str) -> Vec<Attr> {
|
|
96
|
+
self.finish(set, true, false)
|
|
97
|
+
}
|
|
98
|
+
/// Absence of the loaded key.
|
|
99
|
+
pub(super) fn absent(&mut self, set: &str) -> Vec<Attr> {
|
|
100
|
+
self.finish(set, false, true)
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
/// Stores `length` bytes of map data from the first key register into a
|
|
104
|
+
/// conntrack key: the zone (17) or the label (13).
|
|
105
|
+
pub(super) fn set_ct_from_data(key: u32) -> Attr {
|
|
106
|
+
expr("ct", vec![Attr::u32(2, key), Attr::u32(4, register(4))])
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
pub(super) fn word(bytes: &[u8]) -> Vec<u8> {
|
|
110
|
+
let mut result = bytes.to_vec();
|
|
111
|
+
result.resize(bytes.len().div_ceil(4) * 4, 0);
|
|
112
|
+
result
|
|
113
|
+
}
|
|
114
|
+
/// Element bounds built field by field: an exact field repeats in both bounds.
|
|
115
|
+
#[derive(Default)]
|
|
116
|
+
pub(super) struct Key {
|
|
117
|
+
first: Vec<u8>,
|
|
118
|
+
last: Vec<u8>,
|
|
119
|
+
}
|
|
120
|
+
impl Key {
|
|
121
|
+
pub(super) fn exact(self, bytes: &[u8]) -> Self {
|
|
122
|
+
self.range(bytes, bytes)
|
|
123
|
+
}
|
|
124
|
+
pub(super) fn range(mut self, first: &[u8], last: &[u8]) -> Self {
|
|
125
|
+
self.first.extend(word(first));
|
|
126
|
+
self.last.extend(word(last));
|
|
127
|
+
self
|
|
128
|
+
}
|
|
129
|
+
pub(super) fn span(self, first: u16, last: u16) -> Self {
|
|
130
|
+
self.range(&first.to_be_bytes(), &last.to_be_bytes())
|
|
131
|
+
}
|
|
132
|
+
/// One port, or every port for none.
|
|
133
|
+
pub(super) fn port(self, port: Option<u16>) -> Self {
|
|
134
|
+
match port {
|
|
135
|
+
Some(port) => self.span(port, port),
|
|
136
|
+
None => self.span(0, u16::MAX),
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
pub(super) fn index(self, index: u32) -> Self {
|
|
140
|
+
self.exact(&index.to_ne_bytes())
|
|
141
|
+
}
|
|
142
|
+
pub(super) fn link(self, link: &LocalLink) -> Self {
|
|
143
|
+
self.index(link.interface_index).name(&link.interface_name)
|
|
144
|
+
}
|
|
145
|
+
pub(super) fn name(self, name: &str) -> Self {
|
|
146
|
+
let mut bytes = name.as_bytes().to_vec();
|
|
147
|
+
bytes.resize(16, 0);
|
|
148
|
+
self.exact(&bytes)
|
|
149
|
+
}
|
|
150
|
+
pub(super) fn protocol(self, name: &str) -> Self {
|
|
151
|
+
self.exact(&[protocol(name)])
|
|
152
|
+
}
|
|
153
|
+
pub(super) fn protocols(self, name: &str) -> Self {
|
|
154
|
+
self.protocol(name).protocol(name)
|
|
155
|
+
}
|
|
156
|
+
pub(super) fn address(self, address: &str) -> Result<Self> {
|
|
157
|
+
Ok(self.exact(&super::super::address(address)?.to_be_bytes()))
|
|
158
|
+
}
|
|
159
|
+
/// Every address of a canonical prefix.
|
|
160
|
+
pub(super) fn prefix(self, value: &str) -> Result<Self> {
|
|
161
|
+
let (address, mask) = super::super::prefix(value)?;
|
|
162
|
+
Ok(self.range(&address.to_be_bytes(), &(address | !mask).to_be_bytes()))
|
|
163
|
+
}
|
|
164
|
+
pub(super) fn zone(self, zone: u16) -> Self {
|
|
165
|
+
self.exact(&zone.to_ne_bytes())
|
|
166
|
+
}
|
|
167
|
+
pub(super) fn element(self, data: Option<Vec<u8>>) -> IntervalElement {
|
|
168
|
+
IntervalElement {
|
|
169
|
+
first: self.first,
|
|
170
|
+
last: self.last,
|
|
171
|
+
data,
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/// The byte range of each field inside the padded key.
|
|
177
|
+
fn layout(fields: &[u32]) -> Vec<std::ops::Range<usize>> {
|
|
178
|
+
let mut at = 0;
|
|
179
|
+
fields
|
|
180
|
+
.iter()
|
|
181
|
+
.map(|length| {
|
|
182
|
+
let range = at..at + *length as usize;
|
|
183
|
+
at += length.div_ceil(4) as usize * 4;
|
|
184
|
+
range
|
|
185
|
+
})
|
|
186
|
+
.collect()
|
|
187
|
+
}
|
|
188
|
+
fn overlaps(layout: &[std::ops::Range<usize>], a: &IntervalElement, b: &IntervalElement) -> bool {
|
|
189
|
+
layout.iter().all(|field| {
|
|
190
|
+
a.first[field.clone()] <= b.last[field.clone()]
|
|
191
|
+
&& b.first[field.clone()] <= a.last[field.clone()]
|
|
192
|
+
})
|
|
193
|
+
}
|
|
194
|
+
/// Big-endian successor and predecessor of a field value; only called strictly
|
|
195
|
+
/// inside a range, so they never wrap.
|
|
196
|
+
fn step(value: &mut [u8], up: bool) {
|
|
197
|
+
for byte in value.iter_mut().rev() {
|
|
198
|
+
if up {
|
|
199
|
+
let (next, carry) = byte.overflowing_add(1);
|
|
200
|
+
*byte = next;
|
|
201
|
+
if !carry {
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
} else {
|
|
205
|
+
let (next, borrow) = byte.overflowing_sub(1);
|
|
206
|
+
*byte = next;
|
|
207
|
+
if !borrow {
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
/// `a` without `b`, as disjoint pieces; `a` and `b` overlap.
|
|
214
|
+
fn subtract(
|
|
215
|
+
layout: &[std::ops::Range<usize>],
|
|
216
|
+
mut a: IntervalElement,
|
|
217
|
+
b: &IntervalElement,
|
|
218
|
+
) -> Vec<IntervalElement> {
|
|
219
|
+
let mut pieces = Vec::new();
|
|
220
|
+
for field in layout {
|
|
221
|
+
if a.first[field.clone()] < b.first[field.clone()] {
|
|
222
|
+
let mut piece = a.clone();
|
|
223
|
+
piece.last[field.clone()].copy_from_slice(&b.first[field.clone()]);
|
|
224
|
+
step(&mut piece.last[field.clone()], false);
|
|
225
|
+
pieces.push(piece);
|
|
226
|
+
a.first[field.clone()].copy_from_slice(&b.first[field.clone()]);
|
|
227
|
+
}
|
|
228
|
+
if a.last[field.clone()] > b.last[field.clone()] {
|
|
229
|
+
let mut piece = a.clone();
|
|
230
|
+
piece.first[field.clone()].copy_from_slice(&b.last[field.clone()]);
|
|
231
|
+
step(&mut piece.first[field.clone()], true);
|
|
232
|
+
pieces.push(piece);
|
|
233
|
+
a.last[field.clone()].copy_from_slice(&b.last[field.clone()]);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
pieces
|
|
237
|
+
}
|
|
238
|
+
/// The union of elements as disjoint elements, which the kernel's interval
|
|
239
|
+
/// backend requires: it detects an overlap only at an element's bounds. Elements
|
|
240
|
+
/// of a map may overlap only where they carry the same data; anything else is a
|
|
241
|
+
/// policy the compiler cannot express and rejects.
|
|
242
|
+
pub(super) fn disjoint(
|
|
243
|
+
fields: &[u32],
|
|
244
|
+
mut elements: Vec<IntervalElement>,
|
|
245
|
+
) -> Result<Vec<IntervalElement>> {
|
|
246
|
+
let layout = layout(fields);
|
|
247
|
+
elements.sort();
|
|
248
|
+
let mut result: Vec<IntervalElement> = Vec::new();
|
|
249
|
+
for element in elements {
|
|
250
|
+
let mut pieces = vec![element];
|
|
251
|
+
for existing in &result {
|
|
252
|
+
let mut next = Vec::new();
|
|
253
|
+
for piece in pieces {
|
|
254
|
+
if !overlaps(&layout, &piece, existing) {
|
|
255
|
+
next.push(piece);
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
require(piece.data == existing.data)?;
|
|
259
|
+
next.extend(subtract(&layout, piece, existing));
|
|
260
|
+
}
|
|
261
|
+
pieces = next;
|
|
262
|
+
}
|
|
263
|
+
result.extend(pieces);
|
|
264
|
+
}
|
|
265
|
+
result.sort();
|
|
266
|
+
Ok(result)
|
|
267
|
+
}
|
|
268
|
+
/// An interval set or map with its disjoint elements; an empty one is never
|
|
269
|
+
/// created, so a scope without such entries compiles neither it nor its rules.
|
|
270
|
+
/// Returns whether the set exists.
|
|
271
|
+
pub(super) fn interval(
|
|
272
|
+
program: &mut Program<'_>,
|
|
273
|
+
name: &str,
|
|
274
|
+
id: u32,
|
|
275
|
+
fields: &[u32],
|
|
276
|
+
data: Option<u32>,
|
|
277
|
+
elements: Vec<IntervalElement>,
|
|
278
|
+
) -> Result<bool> {
|
|
279
|
+
if elements.is_empty() {
|
|
280
|
+
return Ok(false);
|
|
281
|
+
}
|
|
282
|
+
let elements = disjoint(fields, elements)?;
|
|
283
|
+
program.interval_set(name, id, fields, data)?;
|
|
284
|
+
program.interval_elements(name, elements)?;
|
|
285
|
+
Ok(true)
|
|
286
|
+
}
|
|
287
|
+
/// An exact-key hash set or map; like `interval`, never created empty.
|
|
288
|
+
pub(super) fn exact(
|
|
289
|
+
program: &mut Program<'_>,
|
|
290
|
+
name: &str,
|
|
291
|
+
id: u32,
|
|
292
|
+
key_length: u32,
|
|
293
|
+
data: Option<u32>,
|
|
294
|
+
mut elements: Vec<(Vec<u8>, Option<Vec<u8>>)>,
|
|
295
|
+
) -> Result<bool> {
|
|
296
|
+
if elements.is_empty() {
|
|
297
|
+
return Ok(false);
|
|
298
|
+
}
|
|
299
|
+
elements.sort();
|
|
300
|
+
elements.dedup();
|
|
301
|
+
require(elements.windows(2).all(|pair| pair[0].0 != pair[1].0))?;
|
|
302
|
+
program.exact_set(name, id, key_length, data)?;
|
|
303
|
+
program.exact_elements(name, elements)?;
|
|
304
|
+
Ok(true)
|
|
305
|
+
}
|
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
//! Leased egress grants compile to a constant number of rules around interval
|
|
2
|
+
//! maps, so the rule graph does not grow with grants or generations. Platform
|
|
3
|
+
//! and public grants keep separate maps because their rules sit on either side of
|
|
4
|
+
//! the protected barrier. Each map carries every operand the per-grant rules
|
|
5
|
+
//! compared: the workload link index (0 for the router itself, whose name the
|
|
6
|
+
//! rules prove through `private_link`), the protocols, the live and originally
|
|
7
|
+
//! tracked tuple and the generation's conntrack zone. Generation labels live in
|
|
8
|
+
//! two exact sets, so a label alone never authorizes a changed grant.
|
|
9
|
+
use super::keys::{self, Key, Loads, ADDRESS, INDEX, LABEL, PORT, PROTOCOL, ZONE};
|
|
10
|
+
use super::private::known;
|
|
11
|
+
use super::*;
|
|
12
|
+
|
|
13
|
+
const LABEL_SET: &str = "leased_label";
|
|
14
|
+
const GENERATION_MAP: &str = "leased_generation";
|
|
15
|
+
const RETURN: [u32; 5] = [PROTOCOL, ADDRESS, ADDRESS, PORT, PORT];
|
|
16
|
+
|
|
17
|
+
/// Platform grants precede the protected barrier and public grants follow it.
|
|
18
|
+
#[derive(Clone, Copy, PartialEq, Eq)]
|
|
19
|
+
pub(super) enum Kind {
|
|
20
|
+
Platform,
|
|
21
|
+
Public,
|
|
22
|
+
}
|
|
23
|
+
impl Kind {
|
|
24
|
+
fn of(grant: &Grant) -> Self {
|
|
25
|
+
match grant.destination {
|
|
26
|
+
Destination::Platform { .. } => Self::Platform,
|
|
27
|
+
Destination::Public { .. } => Self::Public,
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
fn label(self) -> &'static str {
|
|
31
|
+
if self == Self::Platform {
|
|
32
|
+
"platform"
|
|
33
|
+
} else {
|
|
34
|
+
"public"
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
fn returns(self) -> String {
|
|
38
|
+
format!("leased_{}_return", self.label())
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/// The maps of one kind split by whether their grants name a source port: a
|
|
42
|
+
/// grant without one matches every source port, so its keys carry no source
|
|
43
|
+
/// port field at all, and a platform grant's key is then exact. A shape's maps
|
|
44
|
+
/// and rules exist only while it has grants.
|
|
45
|
+
#[derive(Clone, Copy, PartialEq, Eq)]
|
|
46
|
+
struct Shape {
|
|
47
|
+
kind: Kind,
|
|
48
|
+
sourced: bool,
|
|
49
|
+
}
|
|
50
|
+
impl Shape {
|
|
51
|
+
fn of(grant: &Grant) -> Self {
|
|
52
|
+
Self {
|
|
53
|
+
kind: Kind::of(grant),
|
|
54
|
+
sourced: grant.source_port.is_some(),
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
fn all(kind: Kind) -> [Self; 2] {
|
|
58
|
+
[false, true].map(|sourced| Self { kind, sourced })
|
|
59
|
+
}
|
|
60
|
+
fn name(self, part: &str) -> String {
|
|
61
|
+
format!(
|
|
62
|
+
"leased_{}{}_{part}",
|
|
63
|
+
self.kind.label(),
|
|
64
|
+
if self.sourced { "_sourced" } else { "" }
|
|
65
|
+
)
|
|
66
|
+
}
|
|
67
|
+
fn id(self, part: u32) -> u32 {
|
|
68
|
+
48 + part * 4 + u32::from(self.kind == Kind::Public) * 2 + u32::from(self.sourced)
|
|
69
|
+
}
|
|
70
|
+
/// A source port field only where the grants name one, then the
|
|
71
|
+
/// destination port.
|
|
72
|
+
fn ports(self) -> Vec<u32> {
|
|
73
|
+
if self.sourced {
|
|
74
|
+
vec![PORT, PORT]
|
|
75
|
+
} else {
|
|
76
|
+
vec![PORT]
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
fn classification(self) -> Vec<u32> {
|
|
80
|
+
let mut fields = vec![INDEX, PROTOCOL, ADDRESS, ADDRESS];
|
|
81
|
+
fields.extend(self.ports());
|
|
82
|
+
fields
|
|
83
|
+
}
|
|
84
|
+
fn flow(self) -> Vec<u32> {
|
|
85
|
+
let mut fields = vec![INDEX, PROTOCOL, PROTOCOL, ADDRESS, ADDRESS];
|
|
86
|
+
fields.extend(self.ports());
|
|
87
|
+
fields.extend([ADDRESS, ADDRESS]);
|
|
88
|
+
fields.extend(self.ports());
|
|
89
|
+
fields.push(ZONE);
|
|
90
|
+
fields
|
|
91
|
+
}
|
|
92
|
+
fn key_ports(self, key: Key, grant: &Grant) -> Key {
|
|
93
|
+
let key = if self.sourced {
|
|
94
|
+
key.port(grant.source_port)
|
|
95
|
+
} else {
|
|
96
|
+
key
|
|
97
|
+
};
|
|
98
|
+
key.port(grant.destination_port)
|
|
99
|
+
}
|
|
100
|
+
/// Live port loads of one direction: the grant's source port where the
|
|
101
|
+
/// shape names it, then its destination port.
|
|
102
|
+
fn load_ports(self, loads: &mut Loads, reply: bool) {
|
|
103
|
+
if self.sourced {
|
|
104
|
+
loads.port(!reply);
|
|
105
|
+
}
|
|
106
|
+
loads.port(reply);
|
|
107
|
+
}
|
|
108
|
+
fn load_original_ports(self, loads: &mut Loads) {
|
|
109
|
+
if self.sourced {
|
|
110
|
+
loads.original_port(true);
|
|
111
|
+
}
|
|
112
|
+
loads.original_port(false);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
fn grants(scope: &RouterScope) -> impl Iterator<Item = (&Generation, &Grant)> {
|
|
117
|
+
scope.generations.iter().flat_map(|generation| {
|
|
118
|
+
generation
|
|
119
|
+
.grants
|
|
120
|
+
.iter()
|
|
121
|
+
.map(move |grant| (generation, grant))
|
|
122
|
+
})
|
|
123
|
+
}
|
|
124
|
+
fn link_index(scope: &RouterScope, grant: &Grant) -> Result<u32> {
|
|
125
|
+
match &grant.source_endpoint {
|
|
126
|
+
Some(id) => scope
|
|
127
|
+
.endpoints
|
|
128
|
+
.iter()
|
|
129
|
+
.find(|endpoint| endpoint.id == *id)
|
|
130
|
+
.map(|endpoint| endpoint.interface_index)
|
|
131
|
+
.ok_or(Error::Invalid),
|
|
132
|
+
None => Ok(0),
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
fn zone_word(zone: u16) -> Vec<u8> {
|
|
136
|
+
let mut word = zone.to_ne_bytes().to_vec();
|
|
137
|
+
word.resize(4, 0);
|
|
138
|
+
word
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
pub(super) fn sets(program: &mut Program<'_>, scope: &RouterScope) -> Result<()> {
|
|
142
|
+
if grants(scope).next().is_none() {
|
|
143
|
+
return Ok(());
|
|
144
|
+
}
|
|
145
|
+
let mut labels = Vec::new();
|
|
146
|
+
let mut generations = Vec::new();
|
|
147
|
+
for generation in &scope.generations {
|
|
148
|
+
let label = super::label(generation);
|
|
149
|
+
let mut key = zone_word(generation.conntrack_zone);
|
|
150
|
+
key.extend(&label);
|
|
151
|
+
labels.push((key, None));
|
|
152
|
+
generations.push((zone_word(generation.conntrack_zone), Some(label)));
|
|
153
|
+
}
|
|
154
|
+
keys::exact(program, LABEL_SET, 38, 4 + LABEL, None, labels)?;
|
|
155
|
+
keys::exact(program, GENERATION_MAP, 39, 4, Some(LABEL), generations)?;
|
|
156
|
+
for kind in [Kind::Platform, Kind::Public] {
|
|
157
|
+
let mut returns = Vec::new();
|
|
158
|
+
for shape in Shape::all(kind) {
|
|
159
|
+
let (mut classification, mut flows) = (Vec::new(), Vec::new());
|
|
160
|
+
for (generation, grant) in grants(scope).filter(|(_, grant)| Shape::of(grant) == shape)
|
|
161
|
+
{
|
|
162
|
+
let destination = super::super::destination(grant, &scope.protection)?;
|
|
163
|
+
let zone = generation.conntrack_zone;
|
|
164
|
+
let zone_data = Some(zone.to_ne_bytes().to_vec());
|
|
165
|
+
let index = link_index(scope, grant)?;
|
|
166
|
+
let classified = Key::default()
|
|
167
|
+
.index(if index == 0 {
|
|
168
|
+
scope.handoff.interface_index
|
|
169
|
+
} else {
|
|
170
|
+
index
|
|
171
|
+
})
|
|
172
|
+
.protocol(&grant.protocol)
|
|
173
|
+
.prefix(&grant.source_prefix)?
|
|
174
|
+
.prefix(&destination)?;
|
|
175
|
+
classification.push(
|
|
176
|
+
shape
|
|
177
|
+
.key_ports(classified, grant)
|
|
178
|
+
.element(zone_data.clone()),
|
|
179
|
+
);
|
|
180
|
+
returns.push(
|
|
181
|
+
Key::default()
|
|
182
|
+
.protocol(&grant.protocol)
|
|
183
|
+
.prefix(&destination)?
|
|
184
|
+
.address(&generation.transit_source_address)?
|
|
185
|
+
.port(grant.destination_port)
|
|
186
|
+
.span(grant.source_port_range.first, grant.source_port_range.last)
|
|
187
|
+
.element(zone_data),
|
|
188
|
+
);
|
|
189
|
+
let mut translation = super::super::address(&generation.transit_source_address)?
|
|
190
|
+
.to_be_bytes()
|
|
191
|
+
.to_vec();
|
|
192
|
+
translation.extend(keys::word(&grant.source_port_range.first.to_be_bytes()));
|
|
193
|
+
translation.extend(keys::word(&grant.source_port_range.last.to_be_bytes()));
|
|
194
|
+
let live = Key::default()
|
|
195
|
+
.index(index)
|
|
196
|
+
.protocols(&grant.protocol)
|
|
197
|
+
.prefix(&grant.source_prefix)?
|
|
198
|
+
.prefix(&destination)?;
|
|
199
|
+
let original = shape
|
|
200
|
+
.key_ports(live, grant)
|
|
201
|
+
.prefix(&grant.source_prefix)?
|
|
202
|
+
.prefix(&destination)?;
|
|
203
|
+
flows.push(
|
|
204
|
+
shape
|
|
205
|
+
.key_ports(original, grant)
|
|
206
|
+
.zone(zone)
|
|
207
|
+
.element(Some(translation)),
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
keys::interval(
|
|
211
|
+
program,
|
|
212
|
+
&shape.name("zone"),
|
|
213
|
+
shape.id(0),
|
|
214
|
+
&shape.classification(),
|
|
215
|
+
Some(ZONE),
|
|
216
|
+
classification,
|
|
217
|
+
)?;
|
|
218
|
+
keys::interval(
|
|
219
|
+
program,
|
|
220
|
+
&shape.name("flow"),
|
|
221
|
+
shape.id(1),
|
|
222
|
+
&shape.flow(),
|
|
223
|
+
Some(12),
|
|
224
|
+
flows,
|
|
225
|
+
)?;
|
|
226
|
+
}
|
|
227
|
+
let id = 48 + 8 + u32::from(kind == Kind::Public);
|
|
228
|
+
keys::interval(program, &kind.returns(), id, &RETURN, Some(ZONE), returns)?;
|
|
229
|
+
}
|
|
230
|
+
Ok(())
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
fn present(scope: &RouterScope, shape: Shape, local: bool) -> BTreeSet<&str> {
|
|
234
|
+
grants(scope)
|
|
235
|
+
.filter(|(_, grant)| Shape::of(grant) == shape && grant.source_endpoint.is_none() == local)
|
|
236
|
+
.map(|(_, grant)| grant.protocol.as_str())
|
|
237
|
+
.collect()
|
|
238
|
+
}
|
|
239
|
+
fn zone_from_data() -> Attr {
|
|
240
|
+
keys::set_ct_from_data(17)
|
|
241
|
+
}
|
|
242
|
+
/// Raw classification into the generation's zone, before conntrack: workload
|
|
243
|
+
/// flows in prerouting by their workload link, router flows in output toward the
|
|
244
|
+
/// handoff, and their translated returns arriving on the handoff.
|
|
245
|
+
pub(super) fn classification(
|
|
246
|
+
program: &mut Program<'_>,
|
|
247
|
+
scope: &RouterScope,
|
|
248
|
+
kind: Kind,
|
|
249
|
+
) -> Result<()> {
|
|
250
|
+
for shape in Shape::all(kind) {
|
|
251
|
+
let tuple = |incoming: bool| {
|
|
252
|
+
let mut loads = Loads::new();
|
|
253
|
+
loads
|
|
254
|
+
.index(incoming)
|
|
255
|
+
.protocol()
|
|
256
|
+
.address(true)
|
|
257
|
+
.address(false);
|
|
258
|
+
shape.load_ports(&mut loads, false);
|
|
259
|
+
loads
|
|
260
|
+
};
|
|
261
|
+
if !present(scope, shape, false).is_empty() {
|
|
262
|
+
let mut expressions = ipv4();
|
|
263
|
+
expressions.extend(known(true));
|
|
264
|
+
expressions.extend(tuple(true).map(&shape.name("zone")));
|
|
265
|
+
expressions.push(zone_from_data());
|
|
266
|
+
program.end("raw_in", expressions, 1)?;
|
|
267
|
+
}
|
|
268
|
+
if !present(scope, shape, true).is_empty() {
|
|
269
|
+
let mut expressions = ipv4();
|
|
270
|
+
expressions.extend(link(&scope.handoff, false));
|
|
271
|
+
expressions.extend(tuple(false).map(&shape.name("zone")));
|
|
272
|
+
expressions.push(zone_from_data());
|
|
273
|
+
program.end("raw_out", expressions, 1)?;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
if grants(scope).any(|(_, grant)| Kind::of(grant) == kind) {
|
|
277
|
+
let mut expressions = ipv4();
|
|
278
|
+
expressions.extend(link(&scope.handoff, true));
|
|
279
|
+
let mut loads = Loads::new();
|
|
280
|
+
loads
|
|
281
|
+
.protocol()
|
|
282
|
+
.address(true)
|
|
283
|
+
.address(false)
|
|
284
|
+
.port(true)
|
|
285
|
+
.port(false);
|
|
286
|
+
expressions.extend(loads.map(&kind.returns()));
|
|
287
|
+
expressions.push(zone_from_data());
|
|
288
|
+
program.end("raw_return", expressions, 1)?;
|
|
289
|
+
}
|
|
290
|
+
Ok(())
|
|
291
|
+
}
|
|
292
|
+
/// The flow key of one direction: the grant's source and destination are the
|
|
293
|
+
/// packet's own in the original direction and swapped in the reply; the
|
|
294
|
+
/// originally tracked tuple and the zone follow.
|
|
295
|
+
fn flow(shape: Shape, reply: bool) -> Loads {
|
|
296
|
+
let mut loads = Loads::new();
|
|
297
|
+
loads
|
|
298
|
+
.index(!reply)
|
|
299
|
+
.protocols()
|
|
300
|
+
.address(!reply)
|
|
301
|
+
.address(reply);
|
|
302
|
+
shape.load_ports(&mut loads, reply);
|
|
303
|
+
loads.original_address(true).original_address(false);
|
|
304
|
+
shape.load_original_ports(&mut loads);
|
|
305
|
+
loads.zone();
|
|
306
|
+
loads
|
|
307
|
+
}
|
|
308
|
+
fn labelled() -> Vec<Attr> {
|
|
309
|
+
Loads::new().zone().label().lookup(LABEL_SET)
|
|
310
|
+
}
|
|
311
|
+
/// Admissions of one kind in one filter chain. The original direction leaves
|
|
312
|
+
/// through the handoff: ESTABLISHED or NEW with the generation's label, or a
|
|
313
|
+
/// fresh NEW flow (a TCP opening only with SYN) that receives it. The reply
|
|
314
|
+
/// arrives on the handoff ESTABLISHED with the label. Workload flows cross
|
|
315
|
+
/// forward; the router's own leave in output and answer in input.
|
|
316
|
+
pub(super) fn admissions(
|
|
317
|
+
program: &mut Program<'_>,
|
|
318
|
+
scope: &RouterScope,
|
|
319
|
+
chain: &str,
|
|
320
|
+
kind: Kind,
|
|
321
|
+
) -> Result<()> {
|
|
322
|
+
for shape in Shape::all(kind) {
|
|
323
|
+
let name = shape.name("flow");
|
|
324
|
+
let original = match chain {
|
|
325
|
+
"forward" => present(scope, shape, false),
|
|
326
|
+
"output" => present(scope, shape, true),
|
|
327
|
+
_ => BTreeSet::new(),
|
|
328
|
+
};
|
|
329
|
+
let reply = match chain {
|
|
330
|
+
"forward" => !present(scope, shape, false).is_empty(),
|
|
331
|
+
"input" => !present(scope, shape, true).is_empty(),
|
|
332
|
+
_ => false,
|
|
333
|
+
};
|
|
334
|
+
let envelope = |reply: bool, connection_state: u32| {
|
|
335
|
+
let mut result = ipv4();
|
|
336
|
+
result.extend(link(&scope.handoff, reply));
|
|
337
|
+
result.extend(ct(1, None, vec![u8::from(reply)]));
|
|
338
|
+
result.extend(state(connection_state));
|
|
339
|
+
result
|
|
340
|
+
};
|
|
341
|
+
if !original.is_empty() {
|
|
342
|
+
let mut established = envelope(false, 2);
|
|
343
|
+
established.extend(known(true));
|
|
344
|
+
established.extend(flow(shape, false).lookup(&name));
|
|
345
|
+
established.extend(labelled());
|
|
346
|
+
program.end(chain, established, 1)?;
|
|
347
|
+
for transport in &original {
|
|
348
|
+
let opening = || {
|
|
349
|
+
let mut result = envelope(false, 8);
|
|
350
|
+
result.extend(meta(16, vec![protocol(transport)]));
|
|
351
|
+
if *transport == "tcp" {
|
|
352
|
+
result.extend(opening_tcp());
|
|
353
|
+
}
|
|
354
|
+
result.extend(known(true));
|
|
355
|
+
result.extend(flow(shape, false).lookup(&name));
|
|
356
|
+
result
|
|
357
|
+
};
|
|
358
|
+
let mut labelled_opening = opening();
|
|
359
|
+
labelled_opening.extend(labelled());
|
|
360
|
+
program.end(chain, labelled_opening, 1)?;
|
|
361
|
+
let mut fresh = opening();
|
|
362
|
+
fresh.extend(ct(13, None, vec![0; 16]));
|
|
363
|
+
fresh.extend(Loads::new().zone().map(GENERATION_MAP));
|
|
364
|
+
fresh.push(keys::set_ct_from_data(13));
|
|
365
|
+
program.end(chain, fresh, 1)?;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
if reply {
|
|
369
|
+
let mut answer = envelope(true, 2);
|
|
370
|
+
answer.extend(known(false));
|
|
371
|
+
answer.extend(flow(shape, true).lookup(&name));
|
|
372
|
+
answer.extend(labelled());
|
|
373
|
+
program.end(chain, answer, 1)?;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
Ok(())
|
|
377
|
+
}
|
|
378
|
+
/// Source NAT of labelled original flows to the transit address and the grant's
|
|
379
|
+
/// leased port range.
|
|
380
|
+
pub(super) fn translation(
|
|
381
|
+
program: &mut Program<'_>,
|
|
382
|
+
scope: &RouterScope,
|
|
383
|
+
kind: Kind,
|
|
384
|
+
) -> Result<()> {
|
|
385
|
+
for shape in Shape::all(kind) {
|
|
386
|
+
if present(scope, shape, false).is_empty() && present(scope, shape, true).is_empty() {
|
|
387
|
+
continue;
|
|
388
|
+
}
|
|
389
|
+
let mut expressions = ipv4();
|
|
390
|
+
expressions.extend(link(&scope.handoff, false));
|
|
391
|
+
expressions.extend(ct(1, None, vec![0]));
|
|
392
|
+
expressions.extend(labelled());
|
|
393
|
+
expressions.extend(known(true));
|
|
394
|
+
expressions.extend(flow(shape, false).map(&shape.name("flow")));
|
|
395
|
+
expressions.push(super::published::nat(0, 2));
|
|
396
|
+
program.rule("post", expressions)?;
|
|
397
|
+
}
|
|
398
|
+
Ok(())
|
|
399
|
+
}
|