@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.
Files changed (53) hide show
  1. package/changelog.md +26 -0
  2. package/dist_rust/smartnftables_linux_amd64_musl +0 -0
  3. package/dist_rust/smartnftables_linux_amd64_musl.tsrust-build.json +4 -4
  4. package/dist_rust/smartnftables_linux_arm64_musl +0 -0
  5. package/dist_rust/smartnftables_linux_arm64_musl.tsrust-build.json +4 -4
  6. package/dist_ts/00_commitinfo_data.js +1 -1
  7. package/dist_ts/classes.manageddockerforwarding.d.ts +17 -0
  8. package/dist_ts/classes.manageddockerforwarding.js +128 -0
  9. package/dist_ts/classes.managednftables.d.ts +9 -7
  10. package/dist_ts/classes.managednftables.js +2 -2
  11. package/dist_ts/index.d.ts +3 -0
  12. package/dist_ts/index.js +3 -1
  13. package/dist_ts/managed.docker.types.d.ts +78 -0
  14. package/dist_ts/managed.docker.types.js +2 -0
  15. package/dist_ts/managed.egress.types.d.ts +91 -0
  16. package/dist_ts/managed.egress.types.js +2 -0
  17. package/dist_ts/managed.types.d.ts +20 -18
  18. package/package.json +2 -2
  19. package/readme.md +143 -6
  20. package/rust/src/docker.frontend.rs +203 -0
  21. package/rust/src/docker.graph.rs +129 -0
  22. package/rust/src/docker.policy.rs +140 -0
  23. package/rust/src/docker.rs +191 -0
  24. package/rust/src/docker_process_tests.rs +29 -0
  25. package/rust/src/docker_tests.rs +148 -0
  26. package/rust/src/egress.compile.rs +288 -0
  27. package/rust/src/egress.host.rs +103 -0
  28. package/rust/src/egress.router.rs +231 -0
  29. package/rust/src/egress.rs +682 -0
  30. package/rust/src/egress_tests.rs +332 -0
  31. package/rust/src/main.rs +13 -6
  32. package/rust/src/managed.rs +105 -0
  33. package/rust/src/owner.rs +115 -7
  34. package/rust/src/owner_coexistence_tests.rs +77 -0
  35. package/rust/src/owner_egress_identity_tests.rs +155 -0
  36. package/rust/src/owner_egress_tests.rs +153 -0
  37. package/rust/src/owner_egress_traffic_tests.rs +470 -0
  38. package/rust/src/owner_host_traffic_tests.rs +305 -0
  39. package/rust/src/owner_identity_tests.rs +169 -0
  40. package/rust/src/owner_link_tests.rs +119 -0
  41. package/rust/src/owner_packet_fixture.rs +200 -0
  42. package/rust/src/owner_tests.rs +33 -7
  43. package/rust/src/policy.rs +120 -81
  44. package/rust/src/tests.rs +22 -0
  45. package/rust/src/wire.links.rs +279 -0
  46. package/rust/src/wire.rs +16 -66
  47. package/ts/00_commitinfo_data.ts +1 -1
  48. package/ts/classes.manageddockerforwarding.ts +113 -0
  49. package/ts/classes.managednftables.ts +13 -13
  50. package/ts/index.ts +3 -0
  51. package/ts/managed.docker.types.ts +49 -0
  52. package/ts/managed.egress.types.ts +85 -0
  53. 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
+ }