@push.rocks/smartnftables 4.3.1 → 4.5.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 +17 -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 +25 -4
- package/package.json +4 -4
- package/readme.md +89 -2
- package/rust/src/egress.compile.rs +105 -38
- package/rust/src/egress.hostgrant.rs +74 -34
- package/rust/src/egress.keys.rs +383 -52
- package/rust/src/egress.leased.rs +71 -42
- package/rust/src/egress.poolguard.rs +72 -0
- package/rust/src/egress.private.rs +32 -37
- package/rust/src/egress.published.rs +74 -47
- package/rust/src/egress.router.rs +1 -1
- package/rust/src/egress.rs +65 -1
- package/rust/src/egress.workloadgrant.rs +30 -28
- package/rust/src/egress_hostgrant_tests.rs +12 -0
- package/rust/src/egress_keys_tests.rs +150 -0
- package/rust/src/egress_poolguardpublished_tests.rs +418 -0
- package/rust/src/egress_tests.rs +47 -4
- package/rust/src/owner.rs +96 -28
- package/rust/src/owner_listing_tests.rs +634 -0
- package/rust/src/owner_tests.rs +3 -0
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/managed.egress.types.ts +26 -4
|
@@ -6,8 +6,12 @@
|
|
|
6
6
|
//! protocols, the live address and port, and the originally tracked address and
|
|
7
7
|
//! port. A port field is an inclusive interval, so one element holds one port or
|
|
8
8
|
//! a whole range, and every other field is exact.
|
|
9
|
-
use super::hostgrant::
|
|
10
|
-
use super::keys::{
|
|
9
|
+
use super::hostgrant::register;
|
|
10
|
+
use super::keys::{
|
|
11
|
+
self, word, Field, Key, Loads, CT_ORIGINAL_DADDR, CT_ORIGINAL_DPORT, CT_ORIGINAL_SADDR,
|
|
12
|
+
CT_ORIGINAL_SPORT, CT_PROTOCOL, IIF, IIFNAME, IP_DADDR, IP_SADDR, L4PROTO, OIF, OIFNAME,
|
|
13
|
+
TH_DPORT, TH_SPORT,
|
|
14
|
+
};
|
|
11
15
|
use super::*;
|
|
12
16
|
|
|
13
17
|
/// Router handoff arrivals of a publication, classified before conntrack.
|
|
@@ -24,7 +28,39 @@ pub(super) const FORWARD: &str = "published_forward";
|
|
|
24
28
|
/// the data is the source NAT to the outside address and port span.
|
|
25
29
|
pub(super) const SYMMETRIC: &str = "published_symmetric";
|
|
26
30
|
|
|
27
|
-
const
|
|
31
|
+
const ARRIVAL_KEY: [Field; 3] = [L4PROTO, IP_DADDR, TH_DPORT];
|
|
32
|
+
const ENDPOINT_KEY: [Field; 5] = [IIF, IIFNAME, L4PROTO, IP_SADDR, TH_SPORT];
|
|
33
|
+
/// The translation maps' key, and their data: the inside address and port, or
|
|
34
|
+
/// the inside address alone.
|
|
35
|
+
const TRANSLATION_KEY: [Field; 4] = [L4PROTO, CT_PROTOCOL, IP_DADDR, TH_DPORT];
|
|
36
|
+
const PORT_DATA: [Field; 2] = [IP_DADDR, TH_DPORT];
|
|
37
|
+
const ADDRESS_DATA: [Field; 1] = [IP_DADDR];
|
|
38
|
+
/// `[link, protocols, address, port, original address, original port]`, the key
|
|
39
|
+
/// of both admission sets, as their original direction loads it: inbound
|
|
40
|
+
/// toward the inside link and destination, symmetric from them.
|
|
41
|
+
const INBOUND_KEY: [Field; 8] = [
|
|
42
|
+
OIF,
|
|
43
|
+
OIFNAME,
|
|
44
|
+
L4PROTO,
|
|
45
|
+
CT_PROTOCOL,
|
|
46
|
+
IP_DADDR,
|
|
47
|
+
TH_DPORT,
|
|
48
|
+
CT_ORIGINAL_DADDR,
|
|
49
|
+
CT_ORIGINAL_DPORT,
|
|
50
|
+
];
|
|
51
|
+
const SYMMETRIC_KEY: [Field; 8] = [
|
|
52
|
+
IIF,
|
|
53
|
+
IIFNAME,
|
|
54
|
+
L4PROTO,
|
|
55
|
+
CT_PROTOCOL,
|
|
56
|
+
IP_SADDR,
|
|
57
|
+
TH_SPORT,
|
|
58
|
+
CT_ORIGINAL_SADDR,
|
|
59
|
+
CT_ORIGINAL_SPORT,
|
|
60
|
+
];
|
|
61
|
+
/// The source NAT a symmetric flow stores: the outside address and the first
|
|
62
|
+
/// and last port of its span.
|
|
63
|
+
const SYMMETRIC_DATA: [Field; 3] = [IP_SADDR, TH_SPORT, TH_SPORT];
|
|
28
64
|
|
|
29
65
|
/// One publication on one hop. The outside is what clients reach (the uplink
|
|
30
66
|
/// address and port on the host, the transit address and port in the router);
|
|
@@ -44,13 +80,6 @@ pub(super) struct Hop<'a> {
|
|
|
44
80
|
pub(super) fixed: &'a LocalLink,
|
|
45
81
|
}
|
|
46
82
|
|
|
47
|
-
/// `[link, protocols, address, port, original address, original port]`, the key
|
|
48
|
-
/// of both admission sets.
|
|
49
|
-
fn flow_fields() -> Vec<u32> {
|
|
50
|
-
let mut fields = LINK.to_vec();
|
|
51
|
-
fields.extend([PROTOCOL, PROTOCOL, ADDRESS, PORT, ADDRESS, PORT]);
|
|
52
|
-
fields
|
|
53
|
-
}
|
|
54
83
|
fn inbound_key(publication: &Publication<'_>) -> Result<IntervalElement> {
|
|
55
84
|
let (outside, outside_first, outside_last) = publication.outside;
|
|
56
85
|
let (inside, inside_first, inside_last) = publication.inside;
|
|
@@ -108,9 +137,6 @@ fn translation_loads() -> Loads {
|
|
|
108
137
|
loads.protocols().address(false).port(false);
|
|
109
138
|
loads
|
|
110
139
|
}
|
|
111
|
-
fn translation_fields() -> [u32; 4] {
|
|
112
|
-
[PROTOCOL, PROTOCOL, ADDRESS, PORT]
|
|
113
|
-
}
|
|
114
140
|
fn ranged(publication: &Publication<'_>) -> bool {
|
|
115
141
|
publication.outside.1 != publication.outside.2
|
|
116
142
|
}
|
|
@@ -133,14 +159,7 @@ pub(super) fn sets(program: &mut Program<'_>, hop: &Hop<'_>, router: bool) -> Re
|
|
|
133
159
|
.element(None),
|
|
134
160
|
);
|
|
135
161
|
}
|
|
136
|
-
declare(
|
|
137
|
-
program,
|
|
138
|
-
ARRIVAL,
|
|
139
|
-
16,
|
|
140
|
-
&[PROTOCOL, ADDRESS, PORT],
|
|
141
|
-
None,
|
|
142
|
-
arrivals,
|
|
143
|
-
)?;
|
|
162
|
+
declare(program, ARRIVAL, 16, &ARRIVAL_KEY, None, arrivals)?;
|
|
144
163
|
// Several publications may share inside ports that are not symmetric;
|
|
145
164
|
// the classification is their union, as disjoint intervals.
|
|
146
165
|
let mut endpoints = Vec::new();
|
|
@@ -155,9 +174,7 @@ pub(super) fn sets(program: &mut Program<'_>, hop: &Hop<'_>, router: bool) -> Re
|
|
|
155
174
|
.element(None),
|
|
156
175
|
);
|
|
157
176
|
}
|
|
158
|
-
|
|
159
|
-
fields.extend([PROTOCOL, ADDRESS, PORT]);
|
|
160
|
-
declare(program, ENDPOINT, 17, &fields, None, endpoints)?;
|
|
177
|
+
declare(program, ENDPOINT, 17, &ENDPOINT_KEY, None, endpoints)?;
|
|
161
178
|
}
|
|
162
179
|
let (mut ports, mut addresses) = (Vec::new(), Vec::new());
|
|
163
180
|
for publication in &hop.publications {
|
|
@@ -175,13 +192,20 @@ pub(super) fn sets(program: &mut Program<'_>, hop: &Hop<'_>, router: bool) -> Re
|
|
|
175
192
|
ports.push(key.element(Some(data)));
|
|
176
193
|
}
|
|
177
194
|
}
|
|
178
|
-
declare(
|
|
195
|
+
declare(
|
|
196
|
+
program,
|
|
197
|
+
PORT_MAP,
|
|
198
|
+
18,
|
|
199
|
+
&TRANSLATION_KEY,
|
|
200
|
+
Some(&PORT_DATA),
|
|
201
|
+
ports,
|
|
202
|
+
)?;
|
|
179
203
|
declare(
|
|
180
204
|
program,
|
|
181
205
|
ADDRESS_MAP,
|
|
182
206
|
19,
|
|
183
|
-
&
|
|
184
|
-
Some(
|
|
207
|
+
&TRANSLATION_KEY,
|
|
208
|
+
Some(&ADDRESS_DATA),
|
|
185
209
|
addresses,
|
|
186
210
|
)?;
|
|
187
211
|
let inbound = hop
|
|
@@ -189,21 +213,28 @@ pub(super) fn sets(program: &mut Program<'_>, hop: &Hop<'_>, router: bool) -> Re
|
|
|
189
213
|
.iter()
|
|
190
214
|
.map(inbound_key)
|
|
191
215
|
.collect::<Result<Vec<_>>>()?;
|
|
192
|
-
declare(program, FORWARD, 20, &
|
|
216
|
+
declare(program, FORWARD, 20, &INBOUND_KEY, None, inbound)?;
|
|
193
217
|
let symmetric = hop
|
|
194
218
|
.publications
|
|
195
219
|
.iter()
|
|
196
220
|
.filter(|publication| publication.symmetric)
|
|
197
221
|
.map(symmetric_key)
|
|
198
222
|
.collect::<Result<Vec<_>>>()?;
|
|
199
|
-
declare(
|
|
223
|
+
declare(
|
|
224
|
+
program,
|
|
225
|
+
SYMMETRIC,
|
|
226
|
+
21,
|
|
227
|
+
&SYMMETRIC_KEY,
|
|
228
|
+
Some(&SYMMETRIC_DATA),
|
|
229
|
+
symmetric,
|
|
230
|
+
)
|
|
200
231
|
}
|
|
201
232
|
fn declare(
|
|
202
233
|
program: &mut Program<'_>,
|
|
203
234
|
name: &str,
|
|
204
235
|
id: u32,
|
|
205
|
-
fields: &[
|
|
206
|
-
data: Option
|
|
236
|
+
fields: &[Field],
|
|
237
|
+
data: Option<&[Field]>,
|
|
207
238
|
elements: Vec<IntervalElement>,
|
|
208
239
|
) -> Result<()> {
|
|
209
240
|
keys::interval(program, name, id, fields, data, elements).map(|_| ())
|
|
@@ -217,6 +248,7 @@ fn admissions(
|
|
|
217
248
|
program: &mut Program<'_>,
|
|
218
249
|
hop: &Hop<'_>,
|
|
219
250
|
set: &str,
|
|
251
|
+
key: &[Field],
|
|
220
252
|
protocols: &BTreeSet<&str>,
|
|
221
253
|
fixed_incoming: bool,
|
|
222
254
|
loads: fn(bool) -> Loads,
|
|
@@ -233,7 +265,7 @@ fn admissions(
|
|
|
233
265
|
result
|
|
234
266
|
};
|
|
235
267
|
let mut established = envelope(false, 2);
|
|
236
|
-
established.extend(loads(false).lookup(set));
|
|
268
|
+
established.extend(loads(false).lookup(set, key)?);
|
|
237
269
|
program.end("forward", established, 1)?;
|
|
238
270
|
for transport in protocols {
|
|
239
271
|
let mut opening = envelope(false, 8);
|
|
@@ -241,11 +273,11 @@ fn admissions(
|
|
|
241
273
|
if *transport == "tcp" {
|
|
242
274
|
opening.extend(opening_tcp());
|
|
243
275
|
}
|
|
244
|
-
opening.extend(loads(false).lookup(set));
|
|
276
|
+
opening.extend(loads(false).lookup(set, key)?);
|
|
245
277
|
program.end("forward", opening, 1)?;
|
|
246
278
|
}
|
|
247
279
|
let mut reply = envelope(true, 2);
|
|
248
|
-
reply.extend(loads(true).lookup(set));
|
|
280
|
+
reply.extend(loads(true).lookup(set, key)?);
|
|
249
281
|
program.end("forward", reply, 1)
|
|
250
282
|
}
|
|
251
283
|
fn protocols<'a>(hop: &Hop<'a>, symmetric: bool) -> BTreeSet<&'a str> {
|
|
@@ -262,6 +294,7 @@ pub(super) fn inbound(program: &mut Program<'_>, hop: &Hop<'_>) -> Result<()> {
|
|
|
262
294
|
program,
|
|
263
295
|
hop,
|
|
264
296
|
FORWARD,
|
|
297
|
+
&INBOUND_KEY,
|
|
265
298
|
&protocols(hop, false),
|
|
266
299
|
true,
|
|
267
300
|
inbound_loads,
|
|
@@ -275,6 +308,7 @@ pub(super) fn symmetric(program: &mut Program<'_>, hop: &Hop<'_>) -> Result<()>
|
|
|
275
308
|
program,
|
|
276
309
|
hop,
|
|
277
310
|
SYMMETRIC,
|
|
311
|
+
&SYMMETRIC_KEY,
|
|
278
312
|
&protocols(hop, true),
|
|
279
313
|
false,
|
|
280
314
|
symmetric_loads,
|
|
@@ -317,7 +351,7 @@ pub(super) fn translations(program: &mut Program<'_>, hop: &Hop<'_>) -> Result<(
|
|
|
317
351
|
] {
|
|
318
352
|
if present {
|
|
319
353
|
let mut expressions = tracked(hop.fixed, true);
|
|
320
|
-
expressions.extend(translation_loads().map(set));
|
|
354
|
+
expressions.extend(translation_loads().map(set, &TRANSLATION_KEY)?);
|
|
321
355
|
expressions.push(nat(1, ports));
|
|
322
356
|
program.rule("pre", expressions)?;
|
|
323
357
|
}
|
|
@@ -329,7 +363,7 @@ pub(super) fn translations(program: &mut Program<'_>, hop: &Hop<'_>) -> Result<(
|
|
|
329
363
|
pub(super) fn symmetric_translation(program: &mut Program<'_>, hop: &Hop<'_>) -> Result<()> {
|
|
330
364
|
if hop.publications.iter().any(|item| item.symmetric) {
|
|
331
365
|
let mut expressions = tracked(hop.fixed, false);
|
|
332
|
-
expressions.extend(symmetric_loads(false).map(SYMMETRIC));
|
|
366
|
+
expressions.extend(symmetric_loads(false).map(SYMMETRIC, &SYMMETRIC_KEY)?);
|
|
333
367
|
expressions.push(nat(0, 2));
|
|
334
368
|
program.rule("post", expressions)?;
|
|
335
369
|
}
|
|
@@ -343,11 +377,8 @@ pub(super) fn arrival(program: &mut Program<'_>, hop: &Hop<'_>) -> Result<()> {
|
|
|
343
377
|
let mut expressions = ipv4();
|
|
344
378
|
expressions.extend(link(hop.fixed, true));
|
|
345
379
|
let mut loads = Loads::new();
|
|
346
|
-
loads
|
|
347
|
-
|
|
348
|
-
.address(false)
|
|
349
|
-
.port(false);
|
|
350
|
-
expressions.extend(loads.lookup(ARRIVAL));
|
|
380
|
+
loads.protocol().address(false).port(false);
|
|
381
|
+
expressions.extend(loads.lookup(ARRIVAL, &ARRIVAL_KEY)?);
|
|
351
382
|
program.end("raw_return", expressions, 1)?;
|
|
352
383
|
}
|
|
353
384
|
Ok(())
|
|
@@ -358,12 +389,8 @@ pub(super) fn endpoint(program: &mut Program<'_>, hop: &Hop<'_>) -> Result<()> {
|
|
|
358
389
|
if !hop.publications.is_empty() {
|
|
359
390
|
let mut expressions = ipv4();
|
|
360
391
|
let mut loads = Loads::new();
|
|
361
|
-
loads
|
|
362
|
-
|
|
363
|
-
.push(|index| meta_to(16, index), PROTOCOL)
|
|
364
|
-
.address(true)
|
|
365
|
-
.port(true);
|
|
366
|
-
expressions.extend(loads.lookup(ENDPOINT));
|
|
392
|
+
loads.link(true).protocol().address(true).port(true);
|
|
393
|
+
expressions.extend(loads.lookup(ENDPOINT, &ENDPOINT_KEY)?);
|
|
367
394
|
program.end("raw_in", expressions, 1)?;
|
|
368
395
|
}
|
|
369
396
|
Ok(())
|
|
@@ -57,7 +57,7 @@ fn host_grant_return(program: &mut Program<'_>, scope: &RouterScope) -> Result<(
|
|
|
57
57
|
if !scope.host_grants.is_empty() {
|
|
58
58
|
let mut expressions = ipv4();
|
|
59
59
|
expressions.extend(link(&scope.handoff, true));
|
|
60
|
-
expressions.extend(hostgrant::arrival_lookup());
|
|
60
|
+
expressions.extend(hostgrant::arrival_lookup()?);
|
|
61
61
|
program.end("raw_return", expressions, 1)?;
|
|
62
62
|
}
|
|
63
63
|
Ok(())
|
package/rust/src/egress.rs
CHANGED
|
@@ -282,15 +282,43 @@ pub struct LocalTcpPortOwner {
|
|
|
282
282
|
pub port: u16,
|
|
283
283
|
pub uid: u32,
|
|
284
284
|
}
|
|
285
|
+
/// One publication a guard admits into a pool: a forwarded flow whose original
|
|
286
|
+
/// destination lies outside every guarded pool, on the published port or range,
|
|
287
|
+
/// translated to exactly `target_address` and the target port(s), and its replies.
|
|
288
|
+
/// A range keeps every port: `target_port` equals `host_port`. Absent for one port.
|
|
289
|
+
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord)]
|
|
290
|
+
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
|
291
|
+
pub struct PoolGuardPublishedPort {
|
|
292
|
+
pub protocol: String,
|
|
293
|
+
pub host_port: u16,
|
|
294
|
+
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
295
|
+
pub host_port_end: Option<u16>,
|
|
296
|
+
pub target_port: u16,
|
|
297
|
+
pub target_address: String,
|
|
298
|
+
}
|
|
299
|
+
impl PoolGuardPublishedPort {
|
|
300
|
+
/// Last published port; equal to `host_port` for one port.
|
|
301
|
+
pub fn host_last(&self) -> u16 {
|
|
302
|
+
self.host_port_end.unwrap_or(self.host_port)
|
|
303
|
+
}
|
|
304
|
+
/// Last target port the publication translates to.
|
|
305
|
+
pub fn target_last(&self) -> u16 {
|
|
306
|
+
self.target_port + (self.host_last() - self.host_port)
|
|
307
|
+
}
|
|
308
|
+
}
|
|
285
309
|
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
|
|
286
310
|
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
|
287
311
|
pub struct AllocationPoolGuardScope {
|
|
288
312
|
pub authority_digest: String,
|
|
289
313
|
pub prefixes: Vec<String>,
|
|
290
|
-
///
|
|
314
|
+
/// Exceptions to the guard: exact host-origin flows into a pool.
|
|
291
315
|
/// Absent and empty are the same canonical policy.
|
|
292
316
|
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
|
293
317
|
pub host_grants: Vec<HostGrant>,
|
|
318
|
+
/// Exceptions to the guard: forwarded publications translated into a pool,
|
|
319
|
+
/// and their replies. Absent and empty are the same canonical policy.
|
|
320
|
+
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
|
321
|
+
pub published_ports: Vec<PoolGuardPublishedPort>,
|
|
294
322
|
/// Host-wide loopback port ownership. Absent and empty are the same
|
|
295
323
|
/// canonical policy.
|
|
296
324
|
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
|
@@ -974,8 +1002,33 @@ fn normalize_pool_guard(value: &mut AllocationPoolGuardScope) -> Result<()> {
|
|
|
974
1002
|
require(covers_address(&value.prefixes, &grant.destination_address)?)
|
|
975
1003
|
})?;
|
|
976
1004
|
value.host_grants = grants;
|
|
1005
|
+
normalize_pool_guard_published(value)?;
|
|
977
1006
|
normalize_local_tcp_port_owners(&mut value.local_tcp_port_owners)
|
|
978
1007
|
}
|
|
1008
|
+
/// A guard publication translates into exactly one address of a guarded pool,
|
|
1009
|
+
/// keeping a range's ports, and publishes each protocol and port at most once,
|
|
1010
|
+
/// as a host-transit scope does: sorted by protocol and first port, disjoint
|
|
1011
|
+
/// neighbours are disjoint spans.
|
|
1012
|
+
fn normalize_pool_guard_published(value: &mut AllocationPoolGuardScope) -> Result<()> {
|
|
1013
|
+
let mut ports = std::mem::take(&mut value.published_ports);
|
|
1014
|
+
crate::capacity("publishedPorts", PUBLISHED_PORTS, ports.len())?;
|
|
1015
|
+
ports.sort();
|
|
1016
|
+
for (index, port) in ports.iter().enumerate() {
|
|
1017
|
+
require(
|
|
1018
|
+
protocol(&port.protocol)
|
|
1019
|
+
&& port.host_port > 0
|
|
1020
|
+
&& port.target_port > 0
|
|
1021
|
+
&& published_span(port.host_port, port.host_port_end, port.target_port)
|
|
1022
|
+
&& (index == 0
|
|
1023
|
+
|| ports[index - 1].protocol != port.protocol
|
|
1024
|
+
|| ports[index - 1].host_last() < port.host_port),
|
|
1025
|
+
)?;
|
|
1026
|
+
unicast(&port.target_address)?;
|
|
1027
|
+
require(covers_address(&value.prefixes, &port.target_address)?)?;
|
|
1028
|
+
}
|
|
1029
|
+
value.published_ports = ports;
|
|
1030
|
+
Ok(())
|
|
1031
|
+
}
|
|
979
1032
|
/// One exact IPv4 loopback host address and port per entry, never a prefix,
|
|
980
1033
|
/// wildcard or range, each with exactly one owning uid. `(uid_t)-1` is not a
|
|
981
1034
|
/// user; it is the kernel's "unchanged" sentinel.
|
|
@@ -1242,6 +1295,9 @@ pub(crate) mod localplatform_tests;
|
|
|
1242
1295
|
#[path = "egress_localport_tests.rs"]
|
|
1243
1296
|
pub(crate) mod localport_tests;
|
|
1244
1297
|
#[cfg(test)]
|
|
1298
|
+
#[path = "egress_poolguardpublished_tests.rs"]
|
|
1299
|
+
mod poolguardpublished_tests;
|
|
1300
|
+
#[cfg(test)]
|
|
1245
1301
|
#[path = "egress_publishedrange_tests.rs"]
|
|
1246
1302
|
mod publishedrange_tests;
|
|
1247
1303
|
#[cfg(test)]
|
|
@@ -1288,6 +1344,14 @@ impl Prepared {
|
|
|
1288
1344
|
pub fn program(&self, table: &str) -> Result<Vec<(u16, Vec<crate::wire::Attr>)>> {
|
|
1289
1345
|
compile::compile(self, table)
|
|
1290
1346
|
}
|
|
1347
|
+
/// Every set the compiled graph declares, with its key and data fields.
|
|
1348
|
+
#[cfg(test)]
|
|
1349
|
+
pub(crate) fn declared(
|
|
1350
|
+
&self,
|
|
1351
|
+
table: &str,
|
|
1352
|
+
) -> Result<std::collections::BTreeMap<String, compile::SetDeclaration>> {
|
|
1353
|
+
compile::declared(self, table)
|
|
1354
|
+
}
|
|
1291
1355
|
pub fn validate_interfaces(&self) -> Result<()> {
|
|
1292
1356
|
let links: Vec<&LocalLink> = match &self.policy.scope {
|
|
1293
1357
|
Scope::RouterEgress(scope) => scope
|
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
//! exact link that holds it (index and name), and both the incoming and the
|
|
7
7
|
//! outgoing link are looked up on every packet. Only the grant's source opens;
|
|
8
8
|
//! the destination only answers.
|
|
9
|
-
use super::hostgrant::{
|
|
9
|
+
use super::hostgrant::{tuple_key, tuple_lookup, TUPLE};
|
|
10
|
+
use super::keys::{Field, Loads, IIF, IIFNAME, IP_SADDR};
|
|
10
11
|
use super::*;
|
|
11
12
|
use std::collections::BTreeSet;
|
|
12
13
|
|
|
@@ -14,16 +15,16 @@ use std::collections::BTreeSet;
|
|
|
14
15
|
pub(super) const TUPLE_SET: &str = "workload_grant";
|
|
15
16
|
/// Every grant address with the exact link that holds it.
|
|
16
17
|
pub(super) const LINK_SET: &str = "workload_link";
|
|
18
|
+
/// A grant address and its link, as the incoming direction loads them.
|
|
19
|
+
const LINK: [Field; 3] = [IIF, IIFNAME, IP_SADDR];
|
|
17
20
|
|
|
18
21
|
/// The link a packet crosses and the address on its side of that link: the
|
|
19
22
|
/// incoming link and the source, or the outgoing link and the destination.
|
|
20
|
-
fn link_lookup(incoming: bool) -> Vec<Attr
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
lookup(LINK_SET),
|
|
26
|
-
]
|
|
23
|
+
fn link_lookup(incoming: bool) -> Result<Vec<Attr>> {
|
|
24
|
+
Loads::new()
|
|
25
|
+
.link(incoming)
|
|
26
|
+
.address(incoming)
|
|
27
|
+
.lookup(LINK_SET, &LINK)
|
|
27
28
|
}
|
|
28
29
|
fn link_key(link: &LocalLink, address: &str) -> Result<Vec<u8>> {
|
|
29
30
|
let mut key = link.interface_index.to_ne_bytes().to_vec();
|
|
@@ -49,9 +50,9 @@ pub(super) fn sets(program: &mut Program<'_>, scope: &RouterScope) -> Result<()>
|
|
|
49
50
|
)?);
|
|
50
51
|
}
|
|
51
52
|
}
|
|
52
|
-
program.
|
|
53
|
+
program.exact_set(TUPLE_SET, 3, &TUPLE, None)?;
|
|
53
54
|
program.elements(TUPLE_SET, keys)?;
|
|
54
|
-
program.
|
|
55
|
+
program.exact_set(LINK_SET, 4, &LINK, None)?;
|
|
55
56
|
program.elements(LINK_SET, links.into_iter().collect())
|
|
56
57
|
}
|
|
57
58
|
|
|
@@ -64,25 +65,26 @@ pub(super) fn admissions(program: &mut Program<'_>, scope: &RouterScope) -> Resu
|
|
|
64
65
|
if scope.workload_grants.is_empty() {
|
|
65
66
|
return Ok(());
|
|
66
67
|
}
|
|
67
|
-
let admission =
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
68
|
+
let admission =
|
|
69
|
+
|reply: bool, connection_state: u32, transport: Option<&str>| -> Result<Vec<Attr>> {
|
|
70
|
+
let mut result = ipv4();
|
|
71
|
+
result.extend(ct(17, None, 0_u16.to_ne_bytes().to_vec()));
|
|
72
|
+
result.extend(ct(1, None, vec![u8::from(reply)]));
|
|
73
|
+
result.extend(state(connection_state));
|
|
74
|
+
if let Some(transport) = transport {
|
|
75
|
+
result.extend(meta(16, vec![protocol(transport)]));
|
|
76
|
+
if transport == "tcp" {
|
|
77
|
+
result.extend(opening_tcp());
|
|
78
|
+
}
|
|
76
79
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
program.end("forward", admission(false, 2, None), 1)?;
|
|
80
|
+
result.extend(link_lookup(true)?);
|
|
81
|
+
result.extend(link_lookup(false)?);
|
|
82
|
+
result.extend(tuple_lookup(TUPLE_SET, false, reply)?);
|
|
83
|
+
Ok(result)
|
|
84
|
+
};
|
|
85
|
+
program.end("forward", admission(false, 2, None)?, 1)?;
|
|
84
86
|
for transport in ["udp", "tcp"] {
|
|
85
|
-
program.end("forward", admission(false, 8, Some(transport))
|
|
87
|
+
program.end("forward", admission(false, 8, Some(transport))?, 1)?;
|
|
86
88
|
}
|
|
87
|
-
program.end("forward", admission(true, 2, None)
|
|
89
|
+
program.end("forward", admission(true, 2, None)?, 1)
|
|
88
90
|
}
|
|
@@ -357,6 +357,18 @@ fn load(flow: &Flow, name: &str, fields: &[Attr]) -> Option<Vec<u8>> {
|
|
|
357
357
|
assert_eq!(loaded.len() as u32, wire::number(fields, 4).unwrap());
|
|
358
358
|
loaded
|
|
359
359
|
}
|
|
360
|
+
"ct" if wire::one(fields, 3).is_ok_and(|direction| direction.value == vec![1]) => {
|
|
361
|
+
// The reply tuple's source is where the flow was translated to: the
|
|
362
|
+
// live destination of the original direction, the live source of the
|
|
363
|
+
// reply, as a filter hook after destination NAT sees them.
|
|
364
|
+
match wire::number(fields, 2).unwrap() {
|
|
365
|
+
19 if flow.reply => flow.source.to_vec(),
|
|
366
|
+
19 => flow.destination.to_vec(),
|
|
367
|
+
11 if flow.reply => flow.source_port.to_be_bytes().to_vec(),
|
|
368
|
+
11 => flow.destination_port.to_be_bytes().to_vec(),
|
|
369
|
+
key => panic!("ct reply {key}"),
|
|
370
|
+
}
|
|
371
|
+
}
|
|
360
372
|
"ct" => {
|
|
361
373
|
if let Ok(direction) = wire::one(fields, 3) {
|
|
362
374
|
assert_eq!(direction.value, vec![0]);
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
//! Set declarations against what nftables 1.0.9 itself writes. Each expected
|
|
2
|
+
//! value was dumped from `nft --debug=mnl add set|map ...` with the host nft
|
|
3
|
+
//! 1.0.9 (libnftnl 1.2.6) on x86_64; userdata numbers are host byte order.
|
|
4
|
+
use super::*;
|
|
5
|
+
|
|
6
|
+
fn hex(text: &str) -> Vec<u8> {
|
|
7
|
+
let digits: String = text.split_whitespace().collect();
|
|
8
|
+
(0..digits.len())
|
|
9
|
+
.step_by(2)
|
|
10
|
+
.map(|at| u8::from_str_radix(&digits[at..at + 2], 16).unwrap())
|
|
11
|
+
.collect()
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/// `typeof ip saddr . th dport . ct original ip saddr . ct zone`
|
|
15
|
+
#[test]
|
|
16
|
+
#[cfg(target_endian = "little")]
|
|
17
|
+
fn concatenated_key_types_and_userdata_are_nfts() {
|
|
18
|
+
let key = [IP_SADDR, TH_DPORT, CT_ORIGINAL_SADDR, CT_ZONE];
|
|
19
|
+
assert_eq!(datatype(&key), 0x001c_d1c4);
|
|
20
|
+
assert_eq!(key_length(&key), 16);
|
|
21
|
+
assert_eq!(
|
|
22
|
+
userdata(&key, None).unwrap(),
|
|
23
|
+
hex("00 04 00000000 03 60 00 04 0d000000 01 58
|
|
24
|
+
00 14 00 04 07000000 01 0c 00 04 0c000000 01 04 0b000000
|
|
25
|
+
01 14 00 04 07000000 01 0c 00 04 0b000000 01 04 02000000
|
|
26
|
+
02 14 00 04 0c000000 01 0c 00 04 13000000 01 04 00000000
|
|
27
|
+
03 14 00 04 0c000000 01 0c 00 04 11000000 01 04 ffffffff")
|
|
28
|
+
);
|
|
29
|
+
// `typeof meta oif . meta oifname . ct reply ip saddr . ct reply proto-src`
|
|
30
|
+
let key = [OIF, OIFNAME, CT_REPLY_SADDR, CT_REPLY_SPORT];
|
|
31
|
+
assert_eq!(datatype(&key), 0x0052_91cd);
|
|
32
|
+
assert_eq!(key_length(&key), 28);
|
|
33
|
+
assert_eq!(
|
|
34
|
+
userdata(&key, None).unwrap(),
|
|
35
|
+
hex("00 04 00000000 03 54 00 04 0d000000 01 4c
|
|
36
|
+
00 0e 00 04 09000000 01 06 00 04 05000000
|
|
37
|
+
01 0e 00 04 09000000 01 06 00 04 07000000
|
|
38
|
+
02 14 00 04 0c000000 01 0c 00 04 13000000 01 04 01000000
|
|
39
|
+
03 14 00 04 0c000000 01 0c 00 04 0b000000 01 04 01000000")
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/// `typeof meta l4proto . ct protocol . ip daddr : ip saddr . th sport . th dport`
|
|
44
|
+
/// and `typeof ct zone : ct label`.
|
|
45
|
+
#[test]
|
|
46
|
+
#[cfg(target_endian = "little")]
|
|
47
|
+
fn map_data_types_and_userdata_are_nfts() {
|
|
48
|
+
let key = [L4PROTO, CT_PROTOCOL, IP_DADDR];
|
|
49
|
+
let data = [IP_SADDR, TH_SPORT, TH_DPORT];
|
|
50
|
+
assert_eq!(datatype(&key), 0xc307);
|
|
51
|
+
assert_eq!(datatype(&data), 0x734d);
|
|
52
|
+
assert_eq!((key_length(&key), data_length(&data)), (12, 12));
|
|
53
|
+
assert_eq!(
|
|
54
|
+
userdata(&key, Some(&data)).unwrap(),
|
|
55
|
+
hex("00 04 00000000 01 04 00000000
|
|
56
|
+
03 44 00 04 0d000000 01 3c
|
|
57
|
+
00 0e 00 04 09000000 01 06 00 04 10000000
|
|
58
|
+
01 14 00 04 0c000000 01 0c 00 04 0a000000 01 04 ffffffff
|
|
59
|
+
02 14 00 04 07000000 01 0c 00 04 0c000000 01 04 0c000000
|
|
60
|
+
04 4a 00 04 0d000000 01 42
|
|
61
|
+
00 14 00 04 07000000 01 0c 00 04 0c000000 01 04 0b000000
|
|
62
|
+
01 14 00 04 07000000 01 0c 00 04 0b000000 01 04 01000000
|
|
63
|
+
02 14 00 04 07000000 01 0c 00 04 0b000000 01 04 02000000
|
|
64
|
+
06 04 00000000")
|
|
65
|
+
);
|
|
66
|
+
let (key, data) = ([CT_ZONE], [CT_LABEL]);
|
|
67
|
+
assert_eq!((datatype(&key), datatype(&data)), (4, 30));
|
|
68
|
+
assert_eq!(data_length(&data), 16);
|
|
69
|
+
assert_eq!(
|
|
70
|
+
userdata(&key, Some(&data)).unwrap(),
|
|
71
|
+
hex("00 04 01000000 01 04 01000000
|
|
72
|
+
03 14 00 04 0c000000 01 0c 00 04 11000000 01 04 ffffffff
|
|
73
|
+
04 14 00 04 0c000000 01 0c 00 04 0d000000 01 04 ffffffff
|
|
74
|
+
06 04 00000000")
|
|
75
|
+
);
|
|
76
|
+
// nft declares the zone alone as its 2 bytes; this map keeps the zone
|
|
77
|
+
// padded to its register, as every release before typed sets did.
|
|
78
|
+
assert_eq!(key_length(&key), 4);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/// Beyond four fields nft 1.0.9 cannot describe a key by `typeof`: its
|
|
82
|
+
/// concatenation userdata stops at `NFT_REG32_SIZE` (4) entries, and the type
|
|
83
|
+
/// keeps 32 bits, six per field, dropping what a longer key shifts out.
|
|
84
|
+
#[test]
|
|
85
|
+
fn longer_keys_carry_their_type_and_byte_order_alone() {
|
|
86
|
+
let key = [IIF, L4PROTO, IP_SADDR, IP_DADDR, TH_DPORT];
|
|
87
|
+
assert_eq!(
|
|
88
|
+
datatype(&key),
|
|
89
|
+
(20 << 24) | (12 << 18) | (7 << 12) | (7 << 6) | 13
|
|
90
|
+
);
|
|
91
|
+
assert_eq!(userdata(&key, None).unwrap(), hex("00 04 00000000"));
|
|
92
|
+
let flow = [
|
|
93
|
+
IIF,
|
|
94
|
+
L4PROTO,
|
|
95
|
+
CT_PROTOCOL,
|
|
96
|
+
IP_SADDR,
|
|
97
|
+
IP_DADDR,
|
|
98
|
+
TH_DPORT,
|
|
99
|
+
CT_ORIGINAL_SADDR,
|
|
100
|
+
CT_ORIGINAL_DADDR,
|
|
101
|
+
CT_ORIGINAL_DPORT,
|
|
102
|
+
CT_ZONE,
|
|
103
|
+
];
|
|
104
|
+
let truncated = flow.iter().fold(0_u64, |result, field| {
|
|
105
|
+
(result << 6) | u64::from(field.datatype)
|
|
106
|
+
});
|
|
107
|
+
assert_eq!(u64::from(datatype(&flow)), truncated & u64::from(u32::MAX));
|
|
108
|
+
let data = [IP_SADDR, TH_SPORT, TH_SPORT];
|
|
109
|
+
let mut expected = hex("00 04 00000000 01 04 00000000");
|
|
110
|
+
expected.extend(hex("04 4a 00 04 0d000000 01 42
|
|
111
|
+
00 14 00 04 07000000 01 0c 00 04 0c000000 01 04 0b000000
|
|
112
|
+
01 14 00 04 07000000 01 0c 00 04 0b000000 01 04 01000000
|
|
113
|
+
02 14 00 04 07000000 01 0c 00 04 0b000000 01 04 01000000
|
|
114
|
+
06 04 00000000"));
|
|
115
|
+
#[cfg(target_endian = "little")]
|
|
116
|
+
assert_eq!(userdata(&flow, Some(&data)).unwrap(), expected);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/// A lookup loads every field of the set's key, by length and type; the other
|
|
120
|
+
/// direction's field of the same kind fits, anything else refuses.
|
|
121
|
+
#[test]
|
|
122
|
+
fn lookups_must_fit_the_declared_key() {
|
|
123
|
+
let declared = [IIF, IIFNAME, IP_SADDR];
|
|
124
|
+
assert!(Loads::new()
|
|
125
|
+
.link(true)
|
|
126
|
+
.address(true)
|
|
127
|
+
.lookup("s", &declared)
|
|
128
|
+
.is_ok());
|
|
129
|
+
assert!(Loads::new()
|
|
130
|
+
.link(false)
|
|
131
|
+
.address(false)
|
|
132
|
+
.lookup("s", &declared)
|
|
133
|
+
.is_ok());
|
|
134
|
+
assert_eq!(
|
|
135
|
+
Loads::new().link(true).port(true).lookup("s", &declared),
|
|
136
|
+
Err(Error::Invalid)
|
|
137
|
+
);
|
|
138
|
+
assert_eq!(
|
|
139
|
+
Loads::new().link(true).lookup("s", &declared),
|
|
140
|
+
Err(Error::Invalid)
|
|
141
|
+
);
|
|
142
|
+
assert_eq!(
|
|
143
|
+
Loads::new()
|
|
144
|
+
.index(true)
|
|
145
|
+
.address(true)
|
|
146
|
+
.address(true)
|
|
147
|
+
.lookup("s", &declared),
|
|
148
|
+
Err(Error::Invalid)
|
|
149
|
+
);
|
|
150
|
+
}
|