@push.rocks/smartnftables 1.4.0 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/changelog.md +26 -0
- package/dist_rust/smartnftables_linux_amd64_musl +0 -0
- package/dist_rust/smartnftables_linux_amd64_musl.tsrust-build.json +4 -4
- package/dist_rust/smartnftables_linux_arm64_musl +0 -0
- package/dist_rust/smartnftables_linux_arm64_musl.tsrust-build.json +4 -4
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/classes.manageddockerforwarding.d.ts +17 -0
- package/dist_ts/classes.manageddockerforwarding.js +128 -0
- package/dist_ts/classes.managednftables.d.ts +9 -7
- package/dist_ts/classes.managednftables.js +2 -2
- package/dist_ts/index.d.ts +3 -0
- package/dist_ts/index.js +3 -1
- package/dist_ts/managed.docker.types.d.ts +78 -0
- package/dist_ts/managed.docker.types.js +2 -0
- package/dist_ts/managed.egress.types.d.ts +91 -0
- package/dist_ts/managed.egress.types.js +2 -0
- package/dist_ts/managed.types.d.ts +20 -18
- package/package.json +2 -2
- package/readme.md +143 -6
- package/rust/src/docker.frontend.rs +203 -0
- package/rust/src/docker.graph.rs +129 -0
- package/rust/src/docker.policy.rs +140 -0
- package/rust/src/docker.rs +191 -0
- package/rust/src/docker_process_tests.rs +29 -0
- package/rust/src/docker_tests.rs +148 -0
- package/rust/src/egress.compile.rs +288 -0
- package/rust/src/egress.host.rs +103 -0
- package/rust/src/egress.router.rs +231 -0
- package/rust/src/egress.rs +682 -0
- package/rust/src/egress_tests.rs +332 -0
- package/rust/src/main.rs +13 -6
- package/rust/src/managed.rs +105 -0
- package/rust/src/owner.rs +115 -7
- package/rust/src/owner_coexistence_tests.rs +77 -0
- package/rust/src/owner_egress_identity_tests.rs +155 -0
- package/rust/src/owner_egress_tests.rs +153 -0
- package/rust/src/owner_egress_traffic_tests.rs +470 -0
- package/rust/src/owner_host_traffic_tests.rs +305 -0
- package/rust/src/owner_identity_tests.rs +169 -0
- package/rust/src/owner_link_tests.rs +119 -0
- package/rust/src/owner_packet_fixture.rs +200 -0
- package/rust/src/owner_tests.rs +33 -7
- package/rust/src/policy.rs +120 -81
- package/rust/src/tests.rs +22 -0
- package/rust/src/wire.links.rs +279 -0
- package/rust/src/wire.rs +16 -66
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.manageddockerforwarding.ts +113 -0
- package/ts/classes.managednftables.ts +13 -13
- package/ts/index.ts +3 -0
- package/ts/managed.docker.types.ts +49 -0
- package/ts/managed.egress.types.ts +85 -0
- package/ts/managed.types.ts +20 -16
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
use super::*;
|
|
2
|
+
use crate::policy::{compare, expr, ipv4, meta, verdict};
|
|
3
|
+
use crate::wire::{self, Attr};
|
|
4
|
+
|
|
5
|
+
#[path = "egress.host.rs"]
|
|
6
|
+
mod host;
|
|
7
|
+
#[path = "egress.router.rs"]
|
|
8
|
+
mod router;
|
|
9
|
+
|
|
10
|
+
pub(super) struct Program<'a> {
|
|
11
|
+
table: &'a str,
|
|
12
|
+
stamp: String,
|
|
13
|
+
ordinal: usize,
|
|
14
|
+
bytes: usize,
|
|
15
|
+
operations: Vec<(u16, Vec<Attr>)>,
|
|
16
|
+
}
|
|
17
|
+
impl<'a> Program<'a> {
|
|
18
|
+
fn push(&mut self, operation: u16, attributes: Vec<Attr>) -> Result<()> {
|
|
19
|
+
self.bytes += wire::encode_attrs(&attributes).len() + 20;
|
|
20
|
+
// Same atomic replacement reserve as v1: a previous bounded graph must
|
|
21
|
+
// fit its handle-only deletions beside the complete target in 240 KiB.
|
|
22
|
+
require(self.bytes <= 100_000 && self.operations.len() < 768)?;
|
|
23
|
+
self.operations.push((operation, attributes));
|
|
24
|
+
Ok(())
|
|
25
|
+
}
|
|
26
|
+
fn chain(&mut self, name: &str, base: Option<(u32, i32, &str)>) -> Result<()> {
|
|
27
|
+
let mut data = vec![Attr::string(1, self.table), Attr::string(3, name)];
|
|
28
|
+
if let Some((hook, priority, kind)) = base {
|
|
29
|
+
data.extend([
|
|
30
|
+
Attr::nested(4, vec![Attr::u32(1, hook), Attr::u32(2, priority as u32)]),
|
|
31
|
+
Attr::u32(5, 1),
|
|
32
|
+
Attr::string(7, kind),
|
|
33
|
+
Attr::u32(10, 1),
|
|
34
|
+
]);
|
|
35
|
+
}
|
|
36
|
+
self.push(3, data)
|
|
37
|
+
}
|
|
38
|
+
fn rule(&mut self, chain: &str, expressions: Vec<Attr>) -> Result<()> {
|
|
39
|
+
let stamp = format!("{}:{}", self.stamp, self.ordinal);
|
|
40
|
+
self.ordinal += 1;
|
|
41
|
+
self.push(
|
|
42
|
+
6,
|
|
43
|
+
vec![
|
|
44
|
+
Attr::string(1, self.table),
|
|
45
|
+
Attr::string(2, chain),
|
|
46
|
+
Attr::nested(4, expressions),
|
|
47
|
+
Attr::bytes(7, stamp.into_bytes()),
|
|
48
|
+
],
|
|
49
|
+
)
|
|
50
|
+
}
|
|
51
|
+
fn end(&mut self, chain: &str, mut expressions: Vec<Attr>, code: i32) -> Result<()> {
|
|
52
|
+
expressions.push(verdict(code, None));
|
|
53
|
+
self.rule(chain, expressions)
|
|
54
|
+
}
|
|
55
|
+
fn jump(&mut self, chain: &str, mut expressions: Vec<Attr>, target: &str) -> Result<()> {
|
|
56
|
+
expressions.push(verdict(-3, Some(target)));
|
|
57
|
+
self.rule(chain, expressions)
|
|
58
|
+
}
|
|
59
|
+
fn deny_link(&mut self, chain: &str, link: &LocalLink, incoming: bool) -> Result<()> {
|
|
60
|
+
// Keep denial when either a name or an index changes independently.
|
|
61
|
+
self.end(
|
|
62
|
+
chain,
|
|
63
|
+
meta(
|
|
64
|
+
if incoming { 4 } else { 5 },
|
|
65
|
+
link.interface_index.to_ne_bytes().to_vec(),
|
|
66
|
+
),
|
|
67
|
+
0,
|
|
68
|
+
)?;
|
|
69
|
+
self.end(
|
|
70
|
+
chain,
|
|
71
|
+
meta(
|
|
72
|
+
if incoming { 6 } else { 7 },
|
|
73
|
+
[link.interface_name.as_bytes(), &[0]].concat(),
|
|
74
|
+
),
|
|
75
|
+
0,
|
|
76
|
+
)
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
fn link(value: &LocalLink, incoming: bool) -> Vec<Attr> {
|
|
81
|
+
let mut result = meta(
|
|
82
|
+
if incoming { 4 } else { 5 },
|
|
83
|
+
value.interface_index.to_ne_bytes().to_vec(),
|
|
84
|
+
);
|
|
85
|
+
result.extend(meta(
|
|
86
|
+
if incoming { 6 } else { 7 },
|
|
87
|
+
[value.interface_name.as_bytes(), &[0]].concat(),
|
|
88
|
+
));
|
|
89
|
+
result
|
|
90
|
+
}
|
|
91
|
+
fn load_payload(base: u32, offset: u32, length: u32) -> Attr {
|
|
92
|
+
expr(
|
|
93
|
+
"payload",
|
|
94
|
+
vec![
|
|
95
|
+
Attr::u32(1, 1),
|
|
96
|
+
Attr::u32(2, base),
|
|
97
|
+
Attr::u32(3, offset),
|
|
98
|
+
Attr::u32(4, length),
|
|
99
|
+
],
|
|
100
|
+
)
|
|
101
|
+
}
|
|
102
|
+
fn masked(mask: Vec<u8>, expected: Vec<u8>) -> Vec<Attr> {
|
|
103
|
+
vec![
|
|
104
|
+
expr(
|
|
105
|
+
"bitwise",
|
|
106
|
+
vec![
|
|
107
|
+
Attr::u32(1, 1),
|
|
108
|
+
Attr::u32(2, 1),
|
|
109
|
+
Attr::u32(3, mask.len() as u32),
|
|
110
|
+
Attr::nested(4, vec![Attr::bytes(1, mask.clone())]),
|
|
111
|
+
Attr::nested(5, vec![Attr::bytes(1, vec![0; mask.len()])]),
|
|
112
|
+
Attr::u32(6, 0),
|
|
113
|
+
],
|
|
114
|
+
),
|
|
115
|
+
compare(expected),
|
|
116
|
+
]
|
|
117
|
+
}
|
|
118
|
+
fn current_address(prefix: &str, source: bool) -> Result<Vec<Attr>> {
|
|
119
|
+
let (address, mask) = super::prefix(prefix)?;
|
|
120
|
+
let mut result = vec![load_payload(1, if source { 12 } else { 16 }, 4)];
|
|
121
|
+
result.extend(masked(
|
|
122
|
+
mask.to_be_bytes().to_vec(),
|
|
123
|
+
address.to_be_bytes().to_vec(),
|
|
124
|
+
));
|
|
125
|
+
Ok(result)
|
|
126
|
+
}
|
|
127
|
+
fn current_port(port: u16, source: bool) -> Vec<Attr> {
|
|
128
|
+
vec![
|
|
129
|
+
load_payload(2, if source { 0 } else { 2 }, 2),
|
|
130
|
+
compare(port.to_be_bytes().to_vec()),
|
|
131
|
+
]
|
|
132
|
+
}
|
|
133
|
+
fn load_ct(key: u32, direction: Option<u8>) -> Attr {
|
|
134
|
+
let mut data = vec![Attr::u32(1, 1), Attr::u32(2, key)];
|
|
135
|
+
if let Some(direction) = direction {
|
|
136
|
+
data.push(Attr::bytes(3, vec![direction]));
|
|
137
|
+
}
|
|
138
|
+
expr("ct", data)
|
|
139
|
+
}
|
|
140
|
+
fn ct(key: u32, direction: Option<u8>, value: Vec<u8>) -> Vec<Attr> {
|
|
141
|
+
vec![load_ct(key, direction), compare(value)]
|
|
142
|
+
}
|
|
143
|
+
fn original_address(prefix: &str, source: bool) -> Result<Vec<Attr>> {
|
|
144
|
+
let (address, mask) = super::prefix(prefix)?;
|
|
145
|
+
let mut result = vec![load_ct(if source { 19 } else { 20 }, Some(0))];
|
|
146
|
+
result.extend(masked(
|
|
147
|
+
mask.to_be_bytes().to_vec(),
|
|
148
|
+
address.to_be_bytes().to_vec(),
|
|
149
|
+
));
|
|
150
|
+
Ok(result)
|
|
151
|
+
}
|
|
152
|
+
fn original_port(port: u16, source: bool) -> Vec<Attr> {
|
|
153
|
+
ct(
|
|
154
|
+
if source { 11 } else { 12 },
|
|
155
|
+
Some(0),
|
|
156
|
+
port.to_be_bytes().to_vec(),
|
|
157
|
+
)
|
|
158
|
+
}
|
|
159
|
+
fn protocol(value: &str) -> u8 {
|
|
160
|
+
if value == "tcp" {
|
|
161
|
+
6
|
|
162
|
+
} else {
|
|
163
|
+
17
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
fn ports(first: u16, last: u16) -> Attr {
|
|
167
|
+
expr(
|
|
168
|
+
"range",
|
|
169
|
+
vec![
|
|
170
|
+
Attr::u32(1, 1),
|
|
171
|
+
Attr::u32(2, 0),
|
|
172
|
+
Attr::nested(3, vec![Attr::bytes(1, first.to_be_bytes().to_vec())]),
|
|
173
|
+
Attr::nested(4, vec![Attr::bytes(1, last.to_be_bytes().to_vec())]),
|
|
174
|
+
],
|
|
175
|
+
)
|
|
176
|
+
}
|
|
177
|
+
fn current_range(first: u16, last: u16, source: bool) -> Vec<Attr> {
|
|
178
|
+
vec![
|
|
179
|
+
load_payload(2, if source { 0 } else { 2 }, 2),
|
|
180
|
+
ports(first, last),
|
|
181
|
+
]
|
|
182
|
+
}
|
|
183
|
+
fn original_range(first: u16, last: u16) -> Vec<Attr> {
|
|
184
|
+
vec![load_ct(11, Some(0)), ports(first, last)]
|
|
185
|
+
}
|
|
186
|
+
fn immediate(register: u32, value: Vec<u8>) -> Attr {
|
|
187
|
+
expr(
|
|
188
|
+
"immediate",
|
|
189
|
+
vec![
|
|
190
|
+
Attr::u32(1, register),
|
|
191
|
+
Attr::nested(2, vec![Attr::bytes(1, value)]),
|
|
192
|
+
],
|
|
193
|
+
)
|
|
194
|
+
}
|
|
195
|
+
fn set_ct(key: u32, value: Vec<u8>) -> Vec<Attr> {
|
|
196
|
+
vec![
|
|
197
|
+
immediate(1, value),
|
|
198
|
+
expr("ct", vec![Attr::u32(2, key), Attr::u32(4, 1)]),
|
|
199
|
+
]
|
|
200
|
+
}
|
|
201
|
+
fn label(generation: &Generation) -> Vec<u8> {
|
|
202
|
+
// A label is an opaque canonical 16-byte value, never a host-endian integer.
|
|
203
|
+
generation
|
|
204
|
+
.conntrack_label
|
|
205
|
+
.as_bytes()
|
|
206
|
+
.chunks_exact(2)
|
|
207
|
+
.map(|pair| u8::from_str_radix(std::str::from_utf8(pair).unwrap(), 16).unwrap())
|
|
208
|
+
.collect()
|
|
209
|
+
}
|
|
210
|
+
fn generation(generation: &Generation, direction: u8, labelled: bool) -> Vec<Attr> {
|
|
211
|
+
let mut result = ct(17, None, generation.conntrack_zone.to_ne_bytes().to_vec());
|
|
212
|
+
result.extend(ct(1, None, vec![direction]));
|
|
213
|
+
if labelled {
|
|
214
|
+
result.extend(ct(13, None, label(generation)));
|
|
215
|
+
}
|
|
216
|
+
result
|
|
217
|
+
}
|
|
218
|
+
fn state(value: u32) -> Vec<Attr> {
|
|
219
|
+
ct(0, None, value.to_ne_bytes().to_vec())
|
|
220
|
+
}
|
|
221
|
+
fn opening_tcp() -> Vec<Attr> {
|
|
222
|
+
let mut result = vec![load_payload(2, 13, 1)];
|
|
223
|
+
// SYN set; FIN, RST, ACK clear. ECN negotiation flags remain permitted.
|
|
224
|
+
result.extend(masked(vec![0x17], vec![0x02]));
|
|
225
|
+
result
|
|
226
|
+
}
|
|
227
|
+
fn snat(address: &str, range: Option<(u16, u16)>) -> Result<Vec<Attr>> {
|
|
228
|
+
let mut result = vec![immediate(
|
|
229
|
+
1,
|
|
230
|
+
super::address(address)?.to_be_bytes().to_vec(),
|
|
231
|
+
)];
|
|
232
|
+
let mut data = vec![
|
|
233
|
+
Attr::u32(1, 0),
|
|
234
|
+
Attr::u32(2, 2),
|
|
235
|
+
Attr::u32(3, 1),
|
|
236
|
+
Attr::u32(4, 1),
|
|
237
|
+
];
|
|
238
|
+
if let Some((first, last)) = range {
|
|
239
|
+
result.extend([
|
|
240
|
+
immediate(2, first.to_be_bytes().to_vec()),
|
|
241
|
+
immediate(3, last.to_be_bytes().to_vec()),
|
|
242
|
+
]);
|
|
243
|
+
data.extend([Attr::u32(5, 2), Attr::u32(6, 3)]);
|
|
244
|
+
}
|
|
245
|
+
data.push(Attr::u32(7, if range.is_some() { 3 } else { 1 }));
|
|
246
|
+
result.push(expr("nat", data));
|
|
247
|
+
Ok(result)
|
|
248
|
+
}
|
|
249
|
+
fn tuple(grant: &Grant, protection: &Protection, reply: bool, original: bool) -> Result<Vec<Attr>> {
|
|
250
|
+
let destination = super::destination(grant, protection)?;
|
|
251
|
+
let mut result = ipv4();
|
|
252
|
+
result.extend(meta(16, vec![protocol(&grant.protocol)]));
|
|
253
|
+
result.extend(current_address(&grant.source_prefix, !reply)?);
|
|
254
|
+
result.extend(current_address(&destination, reply)?);
|
|
255
|
+
for (port, source) in [(grant.source_port, !reply), (grant.destination_port, reply)] {
|
|
256
|
+
if let Some(port) = port {
|
|
257
|
+
result.extend(current_port(port, source));
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
if original {
|
|
261
|
+
result.extend(original_address(&grant.source_prefix, true)?);
|
|
262
|
+
result.extend(original_address(&destination, false)?);
|
|
263
|
+
result.extend(ct(10, None, vec![protocol(&grant.protocol)]));
|
|
264
|
+
for (port, source) in [(grant.source_port, true), (grant.destination_port, false)] {
|
|
265
|
+
if let Some(port) = port {
|
|
266
|
+
result.extend(original_port(port, source));
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
Ok(result)
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
pub(super) fn compile(prepared: &Prepared, table: &str) -> Result<Vec<(u16, Vec<Attr>)>> {
|
|
274
|
+
let mut program = Program {
|
|
275
|
+
table,
|
|
276
|
+
stamp: format!("snft2:{}:{}", prepared.policy.revision, prepared.digest),
|
|
277
|
+
ordinal: 0,
|
|
278
|
+
bytes: 0,
|
|
279
|
+
operations: Vec::new(),
|
|
280
|
+
};
|
|
281
|
+
match &prepared.policy.scope {
|
|
282
|
+
Scope::RouterEgress(scope) => {
|
|
283
|
+
router::compile(&mut program, scope, prepared.policy.revision)?
|
|
284
|
+
}
|
|
285
|
+
Scope::HostTransit(scope) => host::compile(&mut program, scope)?,
|
|
286
|
+
}
|
|
287
|
+
Ok(program.operations)
|
|
288
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
use super::*;
|
|
2
|
+
|
|
3
|
+
fn protection(program: &mut Program<'_>, scope: &HostScope, reply: bool) -> Result<()> {
|
|
4
|
+
let chain = if reply {
|
|
5
|
+
"protected_back"
|
|
6
|
+
} else {
|
|
7
|
+
"protected_out"
|
|
8
|
+
};
|
|
9
|
+
for endpoint in &scope.protection.platform_endpoints {
|
|
10
|
+
let mut expressions = ipv4();
|
|
11
|
+
expressions.extend(meta(16, vec![protocol(&endpoint.protocol)]));
|
|
12
|
+
expressions.extend(ct(10, None, vec![protocol(&endpoint.protocol)]));
|
|
13
|
+
let prefix = format!("{}/32", endpoint.address);
|
|
14
|
+
expressions.extend(current_address(&prefix, reply)?);
|
|
15
|
+
expressions.extend(current_port(endpoint.port, reply));
|
|
16
|
+
expressions.extend(original_address(&prefix, false)?);
|
|
17
|
+
expressions.extend(original_port(endpoint.port, false));
|
|
18
|
+
program.end(chain, expressions, -5)?;
|
|
19
|
+
}
|
|
20
|
+
for prefix in &scope.protection.prefixes {
|
|
21
|
+
for original in [false, true] {
|
|
22
|
+
let mut expressions = ipv4();
|
|
23
|
+
expressions.extend(if original {
|
|
24
|
+
original_address(prefix, false)?
|
|
25
|
+
} else {
|
|
26
|
+
current_address(prefix, reply)?
|
|
27
|
+
});
|
|
28
|
+
program.end(chain, expressions, 0)?;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
program.end(chain, vec![], -5)
|
|
32
|
+
}
|
|
33
|
+
fn envelope(
|
|
34
|
+
scope: &HostScope,
|
|
35
|
+
handoff: &HostHandoff,
|
|
36
|
+
allocation: &Allocation,
|
|
37
|
+
range: &PortRange,
|
|
38
|
+
reply: bool,
|
|
39
|
+
) -> Result<Vec<Attr>> {
|
|
40
|
+
let mut expressions = ipv4();
|
|
41
|
+
expressions.extend(link(&handoff.link, !reply));
|
|
42
|
+
expressions.extend(link(&scope.uplink, reply));
|
|
43
|
+
expressions.extend(meta(16, vec![protocol(&range.protocol)]));
|
|
44
|
+
expressions.extend(ct(10, None, vec![protocol(&range.protocol)]));
|
|
45
|
+
expressions.extend(ct(17, None, 0_u16.to_ne_bytes().to_vec()));
|
|
46
|
+
expressions.extend(ct(1, None, vec![u8::from(reply)]));
|
|
47
|
+
let prefix = format!("{}/32", allocation.transit_source_address);
|
|
48
|
+
expressions.extend(current_address(&prefix, !reply)?);
|
|
49
|
+
expressions.extend(current_range(range.first, range.last, !reply));
|
|
50
|
+
expressions.extend(original_address(&prefix, true)?);
|
|
51
|
+
expressions.extend(original_range(range.first, range.last));
|
|
52
|
+
Ok(expressions)
|
|
53
|
+
}
|
|
54
|
+
pub(super) fn compile(program: &mut Program<'_>, scope: &HostScope) -> Result<()> {
|
|
55
|
+
// Filtering runs after destination NAT. Docker has independent filter hooks.
|
|
56
|
+
// This table cannot override another owner's DROP; Docker contribution and
|
|
57
|
+
// the rest of the host path are independently verified by the caller.
|
|
58
|
+
for (name, hook, priority, kind) in [
|
|
59
|
+
("input", 1, 0, "filter"),
|
|
60
|
+
("forward", 2, 0, "filter"),
|
|
61
|
+
("output", 3, 0, "filter"),
|
|
62
|
+
("post", 4, 100, "nat"),
|
|
63
|
+
] {
|
|
64
|
+
program.chain(name, Some((hook, priority, kind)))?;
|
|
65
|
+
}
|
|
66
|
+
for name in ["protected_out", "protected_back"] {
|
|
67
|
+
program.chain(name, None)?;
|
|
68
|
+
}
|
|
69
|
+
protection(program, scope, false)?;
|
|
70
|
+
protection(program, scope, true)?;
|
|
71
|
+
for handoff in &scope.handoffs {
|
|
72
|
+
program.jump("forward", link(&handoff.link, true), "protected_out")?;
|
|
73
|
+
program.jump("forward", link(&handoff.link, false), "protected_back")?;
|
|
74
|
+
}
|
|
75
|
+
for handoff in &scope.handoffs {
|
|
76
|
+
for allocation in &handoff.allocations {
|
|
77
|
+
for range in &allocation.source_port_ranges {
|
|
78
|
+
let original = envelope(scope, handoff, allocation, range, false)?;
|
|
79
|
+
for connection_state in [2, 8] {
|
|
80
|
+
let mut expressions = original.clone();
|
|
81
|
+
expressions.extend(state(connection_state));
|
|
82
|
+
if connection_state == 8 && range.protocol == "tcp" {
|
|
83
|
+
expressions.extend(opening_tcp());
|
|
84
|
+
}
|
|
85
|
+
program.end("forward", expressions, 1)?;
|
|
86
|
+
}
|
|
87
|
+
let mut reply = envelope(scope, handoff, allocation, range, true)?;
|
|
88
|
+
reply.extend(state(2));
|
|
89
|
+
program.end("forward", reply, 1)?;
|
|
90
|
+
let mut nat = original;
|
|
91
|
+
nat.extend(snat(&scope.snat_address, None)?);
|
|
92
|
+
program.rule("post", nat)?;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
for handoff in &scope.handoffs {
|
|
97
|
+
program.deny_link("input", &handoff.link, true)?;
|
|
98
|
+
program.deny_link("output", &handoff.link, false)?;
|
|
99
|
+
program.deny_link("forward", &handoff.link, true)?;
|
|
100
|
+
program.deny_link("forward", &handoff.link, false)?;
|
|
101
|
+
}
|
|
102
|
+
Ok(())
|
|
103
|
+
}
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
use super::*;
|
|
2
|
+
|
|
3
|
+
fn endpoint<'a>(scope: &'a RouterScope, grant: &Grant) -> Option<&'a LocalLink> {
|
|
4
|
+
grant.source_endpoint.as_ref().and_then(|id| {
|
|
5
|
+
let endpoint = scope.endpoints.iter().find(|item| &item.id == id)?;
|
|
6
|
+
scope
|
|
7
|
+
.links
|
|
8
|
+
.iter()
|
|
9
|
+
.find(|item| item.interface_index == endpoint.interface_index)
|
|
10
|
+
})
|
|
11
|
+
}
|
|
12
|
+
fn raw_original(scope: &RouterScope, grant: &Grant) -> Result<Vec<Attr>> {
|
|
13
|
+
let mut result = tuple(grant, &scope.protection, false, false)?;
|
|
14
|
+
result.extend(match endpoint(scope, grant) {
|
|
15
|
+
Some(endpoint) => link(endpoint, true),
|
|
16
|
+
None => link(&scope.handoff, false),
|
|
17
|
+
});
|
|
18
|
+
Ok(result)
|
|
19
|
+
}
|
|
20
|
+
fn filter(scope: &RouterScope, grant: &Grant, reply: bool) -> Result<Vec<Attr>> {
|
|
21
|
+
let mut result = tuple(grant, &scope.protection, reply, true)?;
|
|
22
|
+
result.extend(link(&scope.handoff, reply));
|
|
23
|
+
if let Some(endpoint) = endpoint(scope, grant) {
|
|
24
|
+
result.extend(link(endpoint, !reply));
|
|
25
|
+
}
|
|
26
|
+
Ok(result)
|
|
27
|
+
}
|
|
28
|
+
fn raw_return(scope: &RouterScope, grant: &Grant, generation: &Generation) -> Result<Vec<Attr>> {
|
|
29
|
+
let mut result = ipv4();
|
|
30
|
+
result.extend(link(&scope.handoff, true));
|
|
31
|
+
result.extend(meta(16, vec![protocol(&grant.protocol)]));
|
|
32
|
+
result.extend(current_address(
|
|
33
|
+
&super::super::destination(grant, &scope.protection)?,
|
|
34
|
+
true,
|
|
35
|
+
)?);
|
|
36
|
+
result.extend(current_address(
|
|
37
|
+
&format!("{}/32", generation.transit_source_address),
|
|
38
|
+
false,
|
|
39
|
+
)?);
|
|
40
|
+
if let Some(port) = grant.destination_port {
|
|
41
|
+
result.extend(current_port(port, true));
|
|
42
|
+
}
|
|
43
|
+
result.extend(current_range(
|
|
44
|
+
grant.source_port_range.first,
|
|
45
|
+
grant.source_port_range.last,
|
|
46
|
+
false,
|
|
47
|
+
));
|
|
48
|
+
Ok(result)
|
|
49
|
+
}
|
|
50
|
+
fn protected(
|
|
51
|
+
program: &mut Program<'_>,
|
|
52
|
+
chain: &str,
|
|
53
|
+
scope: &RouterScope,
|
|
54
|
+
reply: bool,
|
|
55
|
+
) -> Result<()> {
|
|
56
|
+
for prefix in &scope.protection.prefixes {
|
|
57
|
+
for original in [false, true] {
|
|
58
|
+
let mut expressions = ipv4();
|
|
59
|
+
expressions.extend(if original {
|
|
60
|
+
original_address(prefix, false)?
|
|
61
|
+
} else {
|
|
62
|
+
current_address(prefix, reply)?
|
|
63
|
+
});
|
|
64
|
+
program.end(chain, expressions, 0)?;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
program.end(chain, vec![], -5)
|
|
68
|
+
}
|
|
69
|
+
fn raw(program: &mut Program<'_>, scope: &RouterScope) -> Result<()> {
|
|
70
|
+
// Handoff ingress must classify only exact leased return envelopes. Unknown
|
|
71
|
+
// ingress never reaches private/default-zone rules, even after a rename.
|
|
72
|
+
program.jump("raw_in", link(&scope.handoff, true), "raw_return")?;
|
|
73
|
+
program.deny_link("raw_in", &scope.handoff, true)?;
|
|
74
|
+
for platform in [true, false] {
|
|
75
|
+
if !platform {
|
|
76
|
+
for prefix in &scope.protection.prefixes {
|
|
77
|
+
for chain in ["raw_in", "raw_out"] {
|
|
78
|
+
let mut expressions = ipv4();
|
|
79
|
+
expressions.extend(current_address(prefix, false)?);
|
|
80
|
+
program.end(chain, expressions, -5)?;
|
|
81
|
+
}
|
|
82
|
+
let mut expressions = ipv4();
|
|
83
|
+
expressions.extend(current_address(prefix, true)?);
|
|
84
|
+
program.end("raw_return", expressions, 0)?;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
for generation in &scope.generations {
|
|
88
|
+
for grant in &generation.grants {
|
|
89
|
+
if matches!(grant.destination, Destination::Platform { .. }) != platform {
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
let mut original = raw_original(scope, grant)?;
|
|
93
|
+
original.extend(set_ct(17, generation.conntrack_zone.to_ne_bytes().to_vec()));
|
|
94
|
+
program.end(
|
|
95
|
+
if grant.source_endpoint.is_some() {
|
|
96
|
+
"raw_in"
|
|
97
|
+
} else {
|
|
98
|
+
"raw_out"
|
|
99
|
+
},
|
|
100
|
+
original,
|
|
101
|
+
1,
|
|
102
|
+
)?;
|
|
103
|
+
let mut reply = raw_return(scope, grant, generation)?;
|
|
104
|
+
reply.extend(set_ct(17, generation.conntrack_zone.to_ne_bytes().to_vec()));
|
|
105
|
+
program.end("raw_return", reply, 1)?;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
program.end("raw_return", vec![], 0)
|
|
110
|
+
}
|
|
111
|
+
fn grants(
|
|
112
|
+
program: &mut Program<'_>,
|
|
113
|
+
scope: &RouterScope,
|
|
114
|
+
chain: &str,
|
|
115
|
+
platform: bool,
|
|
116
|
+
) -> Result<()> {
|
|
117
|
+
for allocation in &scope.generations {
|
|
118
|
+
for grant in &allocation.grants {
|
|
119
|
+
if matches!(grant.destination, Destination::Platform { .. }) != platform {
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
let local = grant.source_endpoint.is_none();
|
|
123
|
+
if chain == "forward" && local || chain != "forward" && !local {
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
if chain != "input" {
|
|
127
|
+
let mut original = filter(scope, grant, false)?;
|
|
128
|
+
original.extend(generation(allocation, 0, false));
|
|
129
|
+
// Both original directions are checked for every packet; a
|
|
130
|
+
// label alone never authorizes a changed grant or endpoint.
|
|
131
|
+
for connection_state in [2, 8] {
|
|
132
|
+
let mut labelled = original.clone();
|
|
133
|
+
labelled.extend(ct(13, None, label(allocation)));
|
|
134
|
+
labelled.extend(state(connection_state));
|
|
135
|
+
if connection_state == 8 && grant.protocol == "tcp" {
|
|
136
|
+
labelled.extend(opening_tcp());
|
|
137
|
+
}
|
|
138
|
+
program.end(chain, labelled, 1)?;
|
|
139
|
+
}
|
|
140
|
+
original.extend(state(8));
|
|
141
|
+
original.extend(ct(13, None, vec![0; 16]));
|
|
142
|
+
if grant.protocol == "tcp" {
|
|
143
|
+
original.extend(opening_tcp());
|
|
144
|
+
}
|
|
145
|
+
original.extend(set_ct(13, label(allocation)));
|
|
146
|
+
program.end(chain, original, 1)?;
|
|
147
|
+
}
|
|
148
|
+
if chain != "output" {
|
|
149
|
+
let mut reply = filter(scope, grant, true)?;
|
|
150
|
+
reply.extend(generation(allocation, 1, true));
|
|
151
|
+
reply.extend(state(2));
|
|
152
|
+
program.end(chain, reply, 1)?;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
Ok(())
|
|
157
|
+
}
|
|
158
|
+
pub(super) fn compile(program: &mut Program<'_>, scope: &RouterScope, revision: u64) -> Result<()> {
|
|
159
|
+
for (name, hook, priority, kind) in [
|
|
160
|
+
("raw_in", 0, -300, "filter"),
|
|
161
|
+
("raw_out", 3, -300, "filter"),
|
|
162
|
+
("input", 1, -150, "filter"),
|
|
163
|
+
("forward", 2, -150, "filter"),
|
|
164
|
+
("output", 3, -150, "filter"),
|
|
165
|
+
("post", 4, 100, "nat"),
|
|
166
|
+
] {
|
|
167
|
+
program.chain(name, Some((hook, priority, kind)))?;
|
|
168
|
+
}
|
|
169
|
+
for name in ["raw_return", "protected_out", "protected_back"] {
|
|
170
|
+
program.chain(name, None)?;
|
|
171
|
+
}
|
|
172
|
+
let private = policy::Policy {
|
|
173
|
+
schema_version: 1,
|
|
174
|
+
revision,
|
|
175
|
+
endpoints: scope.endpoints.clone(),
|
|
176
|
+
rules: scope.rules.clone(),
|
|
177
|
+
}
|
|
178
|
+
.prepare()?;
|
|
179
|
+
for (name, rules) in private.guard_chains()? {
|
|
180
|
+
program.chain(&name, None)?;
|
|
181
|
+
for expressions in rules {
|
|
182
|
+
program.rule(&name, expressions)?;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
raw(program, scope)?;
|
|
186
|
+
protected(program, "protected_out", scope, false)?;
|
|
187
|
+
protected(program, "protected_back", scope, true)?;
|
|
188
|
+
for chain in ["input", "forward", "output"] {
|
|
189
|
+
let parts = private.filter_rules(chain)?;
|
|
190
|
+
for expressions in parts.guards.into_iter().chain(parts.grants) {
|
|
191
|
+
program.rule(chain, expressions)?;
|
|
192
|
+
}
|
|
193
|
+
grants(program, scope, chain, true)?;
|
|
194
|
+
for incoming in [true, false] {
|
|
195
|
+
if chain == "input" && !incoming || chain == "output" && incoming {
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
program.jump(
|
|
199
|
+
chain,
|
|
200
|
+
link(&scope.handoff, incoming),
|
|
201
|
+
if incoming {
|
|
202
|
+
"protected_back"
|
|
203
|
+
} else {
|
|
204
|
+
"protected_out"
|
|
205
|
+
},
|
|
206
|
+
)?;
|
|
207
|
+
}
|
|
208
|
+
grants(program, scope, chain, false)?;
|
|
209
|
+
for expressions in parts.denials {
|
|
210
|
+
program.rule(chain, expressions)?;
|
|
211
|
+
}
|
|
212
|
+
for incoming in [true, false] {
|
|
213
|
+
if chain == "input" && !incoming || chain == "output" && incoming {
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
program.deny_link(chain, &scope.handoff, incoming)?;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
for allocation in &scope.generations {
|
|
220
|
+
for grant in &allocation.grants {
|
|
221
|
+
let mut expressions = filter(scope, grant, false)?;
|
|
222
|
+
expressions.extend(generation(allocation, 0, true));
|
|
223
|
+
expressions.extend(snat(
|
|
224
|
+
&allocation.transit_source_address,
|
|
225
|
+
Some((grant.source_port_range.first, grant.source_port_range.last)),
|
|
226
|
+
)?);
|
|
227
|
+
program.rule("post", expressions)?;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
Ok(())
|
|
231
|
+
}
|