@push.rocks/smartnftables 2.5.1 → 2.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 +19 -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 +44 -0
- package/native-notices/crates/memchr-2.8.3/unlicense +24 -0
- package/native-notices/manifest.json +566 -344
- package/native-notices/readme.md +40 -34
- package/native-notices/runtime/gcc/gpl-3.0.txt +674 -0
- package/native-notices/runtime/gcc/runtime-library-exception-3.1.txt +73 -0
- package/native-notices/{rust-standard-library/dependency-notices.html → rust/copyright-library.html} +185 -991
- package/native-notices/rust/crates/adler2-2.0.1/license-0bsd +12 -0
- package/native-notices/rust/crates/adler2-2.0.1/license-apache +201 -0
- package/native-notices/rust/crates/adler2-2.0.1/license-mit +23 -0
- package/native-notices/rust/crates/memchr-2.7.6/copying +3 -0
- package/native-notices/rust/crates/memchr-2.7.6/license-mit +21 -0
- package/native-notices/rust/crates/memchr-2.7.6/unlicense +24 -0
- package/native-notices/rust/crates/miniz_oxide-0.8.9/license +25 -0
- package/native-notices/{rust-standard-library/llvm-libunwind-license.txt → rust/crates/miniz_oxide-0.8.9/license-apache.md} +11 -145
- package/native-notices/rust/crates/miniz_oxide-0.8.9/license-mit.md +24 -0
- package/native-notices/rust/crates/miniz_oxide-0.8.9/license-zlib.md +14 -0
- package/native-notices/{rust-standard-library → rust/crates/object-0.37.3}/license-apache +25 -0
- package/native-notices/{rust-standard-library → rust/crates/object-0.37.3}/license-mit +1 -1
- package/native-notices/rust/licenses/apache-2.0.txt +73 -0
- package/native-notices/rust/licenses/bsd-2-clause.txt +9 -0
- package/native-notices/rust/licenses/cc-by-sa-4.0.txt +427 -0
- package/native-notices/rust/licenses/gcc-exception-3.1.txt +30 -0
- package/native-notices/rust/licenses/gpl-2.0-only.txt +133 -0
- package/native-notices/rust/licenses/gpl-3.0-or-later.txt +202 -0
- package/native-notices/rust/licenses/isc.txt +7 -0
- package/native-notices/rust/licenses/llvm-exception.txt +15 -0
- package/native-notices/rust/licenses/mit.txt +9 -0
- package/native-notices/rust/licenses/ncsa.txt +29 -0
- package/native-notices/rust/licenses/ofl-1.1.txt +43 -0
- package/native-notices/{rust-standard-library/unicode-license.txt → rust/licenses/unicode-3.0.txt} +1 -1
- package/package.json +4 -5
- package/readme.md +149 -5
- package/rust/src/egress.compile.rs +2 -0
- package/rust/src/egress.host.rs +88 -1
- package/rust/src/egress.hostgrant.rs +13 -13
- package/rust/src/egress.poolguard.rs +45 -0
- package/rust/src/egress.router.rs +3 -1
- package/rust/src/egress.rs +122 -6
- package/rust/src/egress.workloadgrant.rs +88 -0
- package/rust/src/egress_hostgrant_tests.rs +47 -29
- package/rust/src/egress_localplatform_tests.rs +204 -0
- package/rust/src/egress_localport_tests.rs +264 -0
- package/rust/src/egress_workloadgrant_tests.rs +314 -0
- package/rust/src/owner.rs +8 -1
- package/rust/src/owner_hostgrant_traffic_tests.rs +9 -4
- package/rust/src/owner_identity_tests.rs +62 -0
- package/rust/src/owner_localplatform_tests.rs +274 -0
- package/rust/src/owner_localport_tests.rs +241 -0
- package/rust/src/owner_tests.rs +9 -0
- package/rust/src/owner_workloadgrant_tests.rs +278 -0
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/managed.egress.types.ts +46 -0
- package/native-notices/rust-standard-library/cargo.lock +0 -522
- package/scripts/check-native-notices.mjs +0 -27
- /package/native-notices/{rust-standard-library/musl-1.2.5-copyright.txt → runtime/musl-1.2.5/copyright} +0 -0
- /package/native-notices/{rust-standard-library → rust}/compiler-builtins-license.txt +0 -0
- /package/native-notices/{rust-standard-library/addr2line-0.25.1-license-apache → rust/crates/addr2line-0.25.1/license-apache} +0 -0
- /package/native-notices/{rust-standard-library/addr2line-0.25.1-license-mit → rust/crates/addr2line-0.25.1/license-mit} +0 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
//! Workload grants: stateful one-way flows from one workload endpoint to one
|
|
2
|
+
//! exact port of another, both behind the same router. Like host grants they
|
|
3
|
+
//! compile to a constant number of rules around exact set lookups. The tuple key
|
|
4
|
+
//! carries both protocols and the live and originally tracked tuple, so a
|
|
5
|
+
//! translated flow never matches. A second set binds every grant address to the
|
|
6
|
+
//! exact link that holds it (index and name), and both the incoming and the
|
|
7
|
+
//! outgoing link are looked up on every packet. Only the grant's source opens;
|
|
8
|
+
//! the destination only answers.
|
|
9
|
+
use super::hostgrant::{lookup, meta_to, payload_to, tuple_key, tuple_lookup};
|
|
10
|
+
use super::*;
|
|
11
|
+
use std::collections::BTreeSet;
|
|
12
|
+
|
|
13
|
+
/// Every exact workload-to-workload flow of the scope.
|
|
14
|
+
pub(super) const TUPLE_SET: &str = "workload_grant";
|
|
15
|
+
/// Every grant address with the exact link that holds it.
|
|
16
|
+
pub(super) const LINK_SET: &str = "workload_link";
|
|
17
|
+
|
|
18
|
+
/// The link a packet crosses and the address on its side of that link: the
|
|
19
|
+
/// incoming link and the source, or the outgoing link and the destination.
|
|
20
|
+
fn link_lookup(incoming: bool) -> Vec<Attr> {
|
|
21
|
+
vec![
|
|
22
|
+
meta_to(if incoming { 4 } else { 5 }, 4),
|
|
23
|
+
meta_to(if incoming { 6 } else { 7 }, 5),
|
|
24
|
+
payload_to(1, if incoming { 12 } else { 16 }, 4, 9),
|
|
25
|
+
lookup(LINK_SET),
|
|
26
|
+
]
|
|
27
|
+
}
|
|
28
|
+
fn link_key(link: &LocalLink, address: &str) -> Result<Vec<u8>> {
|
|
29
|
+
let mut key = link.interface_index.to_ne_bytes().to_vec();
|
|
30
|
+
let mut name = link.interface_name.as_bytes().to_vec();
|
|
31
|
+
name.resize(16, 0);
|
|
32
|
+
key.extend(name);
|
|
33
|
+
key.extend(super::address(address)?.to_be_bytes());
|
|
34
|
+
Ok(key)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
pub(super) fn sets(program: &mut Program<'_>, scope: &RouterScope) -> Result<()> {
|
|
38
|
+
if scope.workload_grants.is_empty() {
|
|
39
|
+
return Ok(());
|
|
40
|
+
}
|
|
41
|
+
let mut keys = Vec::new();
|
|
42
|
+
let mut links = BTreeSet::new();
|
|
43
|
+
for grant in &scope.workload_grants {
|
|
44
|
+
keys.push(tuple_key(grant, None)?);
|
|
45
|
+
for address in [&grant.source_address, &grant.destination_address] {
|
|
46
|
+
links.insert(link_key(
|
|
47
|
+
super::router::endpoint_link(scope, address)?,
|
|
48
|
+
address,
|
|
49
|
+
)?);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
program.set(TUPLE_SET, 3, 32)?;
|
|
53
|
+
program.elements(TUPLE_SET, keys)?;
|
|
54
|
+
program.set(LINK_SET, 4, 24)?;
|
|
55
|
+
program.elements(LINK_SET, links.into_iter().collect())
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/// Four forward admissions whatever the number of grants: the ESTABLISHED
|
|
59
|
+
/// original direction, a NEW UDP opening, a NEW TCP opening restricted to SYN
|
|
60
|
+
/// with FIN/RST/ACK clear, and the ESTABLISHED reply, each in the default
|
|
61
|
+
/// conntrack zone. A reply is only ever ESTABLISHED, so the destination
|
|
62
|
+
/// workload can never open a flow toward the source through a grant.
|
|
63
|
+
pub(super) fn admissions(program: &mut Program<'_>, scope: &RouterScope) -> Result<()> {
|
|
64
|
+
if scope.workload_grants.is_empty() {
|
|
65
|
+
return Ok(());
|
|
66
|
+
}
|
|
67
|
+
let admission = |reply: bool, connection_state: u32, transport: Option<&str>| {
|
|
68
|
+
let mut result = ipv4();
|
|
69
|
+
result.extend(ct(17, None, 0_u16.to_ne_bytes().to_vec()));
|
|
70
|
+
result.extend(ct(1, None, vec![u8::from(reply)]));
|
|
71
|
+
result.extend(state(connection_state));
|
|
72
|
+
if let Some(transport) = transport {
|
|
73
|
+
result.extend(meta(16, vec![protocol(transport)]));
|
|
74
|
+
if transport == "tcp" {
|
|
75
|
+
result.extend(opening_tcp());
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
result.extend(link_lookup(true));
|
|
79
|
+
result.extend(link_lookup(false));
|
|
80
|
+
result.extend(tuple_lookup(TUPLE_SET, false, reply));
|
|
81
|
+
result
|
|
82
|
+
};
|
|
83
|
+
program.end("forward", admission(false, 2, None), 1)?;
|
|
84
|
+
for transport in ["udp", "tcp"] {
|
|
85
|
+
program.end("forward", admission(false, 8, Some(transport)), 1)?;
|
|
86
|
+
}
|
|
87
|
+
program.end("forward", admission(true, 2, None), 1)
|
|
88
|
+
}
|
|
@@ -15,15 +15,15 @@ pub(crate) fn granted(mut value: Value, grants: Value) -> Value {
|
|
|
15
15
|
value["scope"]["hostGrants"] = grants;
|
|
16
16
|
value
|
|
17
17
|
}
|
|
18
|
-
fn normalized(value: Value) -> crate::Result<Policy> {
|
|
18
|
+
pub(super) fn normalized(value: Value) -> crate::Result<Policy> {
|
|
19
19
|
serde_json::from_value::<Policy>(value)
|
|
20
20
|
.map_err(|_| crate::Error::Invalid)?
|
|
21
21
|
.normalize()
|
|
22
22
|
}
|
|
23
|
-
fn prepared(value: Value) -> Prepared {
|
|
23
|
+
pub(super) fn prepared(value: Value) -> Prepared {
|
|
24
24
|
normalized(value).unwrap().prepare().unwrap()
|
|
25
25
|
}
|
|
26
|
-
fn bytes(prepared: &Prepared) -> Vec<u8> {
|
|
26
|
+
pub(super) fn bytes(prepared: &Prepared) -> Vec<u8> {
|
|
27
27
|
let mut result = Vec::new();
|
|
28
28
|
for (kind, attributes) in prepared.program("snft_host_grant").unwrap() {
|
|
29
29
|
result.extend(kind.to_be_bytes());
|
|
@@ -38,28 +38,36 @@ fn exact() -> Value {
|
|
|
38
38
|
|
|
39
39
|
/// One packet with the conntrack facts a filter hook observes.
|
|
40
40
|
#[derive(Clone, Copy)]
|
|
41
|
-
struct Flow {
|
|
42
|
-
input: Option<(u32, &'static str)>,
|
|
43
|
-
output: Option<(u32, &'static str)>,
|
|
44
|
-
protocol: u8,
|
|
45
|
-
source: [u8; 4],
|
|
46
|
-
destination: [u8; 4],
|
|
47
|
-
source_port: u16,
|
|
48
|
-
destination_port: u16,
|
|
49
|
-
tcp_flags: u8,
|
|
50
|
-
state: u32,
|
|
51
|
-
reply: bool,
|
|
52
|
-
zone: u16,
|
|
41
|
+
pub(super) struct Flow {
|
|
42
|
+
pub(super) input: Option<(u32, &'static str)>,
|
|
43
|
+
pub(super) output: Option<(u32, &'static str)>,
|
|
44
|
+
pub(super) protocol: u8,
|
|
45
|
+
pub(super) source: [u8; 4],
|
|
46
|
+
pub(super) destination: [u8; 4],
|
|
47
|
+
pub(super) source_port: u16,
|
|
48
|
+
pub(super) destination_port: u16,
|
|
49
|
+
pub(super) tcp_flags: u8,
|
|
50
|
+
pub(super) state: u32,
|
|
51
|
+
pub(super) reply: bool,
|
|
52
|
+
pub(super) zone: u16,
|
|
53
53
|
/// Original-direction conntrack tuple: source, destination, ports.
|
|
54
|
-
original: ([u8; 4], [u8; 4], u16, u16),
|
|
54
|
+
pub(super) original: ([u8; 4], [u8; 4], u16, u16),
|
|
55
|
+
/// The file system uid of the packet's user socket; None without one, when
|
|
56
|
+
/// `meta skuid` breaks the rule like the kernel.
|
|
57
|
+
pub(super) uid: Option<u32>,
|
|
55
58
|
}
|
|
56
59
|
const HOST: [u8; 4] = [10, 240, 0, 1];
|
|
57
60
|
const WORK: [u8; 4] = [10, 241, 0, 2];
|
|
58
61
|
const NEW: u32 = 8;
|
|
59
|
-
const ESTABLISHED: u32 = 2;
|
|
62
|
+
pub(super) const ESTABLISHED: u32 = 2;
|
|
60
63
|
const SYN: u8 = 0x02;
|
|
61
|
-
const ACK: u8 = 0x10;
|
|
62
|
-
|
|
64
|
+
pub(super) const ACK: u8 = 0x10;
|
|
65
|
+
/// The `reject with tcp reset` decision, distinct from a plain drop.
|
|
66
|
+
pub(super) const REJECT: i32 = i32::MIN;
|
|
67
|
+
pub(super) fn opening(
|
|
68
|
+
input: Option<(u32, &'static str)>,
|
|
69
|
+
output: Option<(u32, &'static str)>,
|
|
70
|
+
) -> Flow {
|
|
63
71
|
Flow {
|
|
64
72
|
input,
|
|
65
73
|
output,
|
|
@@ -73,6 +81,7 @@ fn opening(input: Option<(u32, &'static str)>, output: Option<(u32, &'static str
|
|
|
73
81
|
reply: false,
|
|
74
82
|
zone: 0,
|
|
75
83
|
original: (HOST, WORK, 443, 8080),
|
|
84
|
+
uid: None,
|
|
76
85
|
}
|
|
77
86
|
}
|
|
78
87
|
/// The workload's answer to `flow`, arriving on the reverse links.
|
|
@@ -181,17 +190,17 @@ fn foreign(flow: Flow) -> Vec<Flow> {
|
|
|
181
190
|
/// kernel does: the terminal verdict, the chain that reached it (`policy` for the
|
|
182
191
|
/// base chain's accept policy) and the conntrack zone a raw rule stored.
|
|
183
192
|
#[derive(Debug, PartialEq, Eq)]
|
|
184
|
-
struct Decision {
|
|
185
|
-
verdict: i32,
|
|
186
|
-
chain: String,
|
|
187
|
-
zone: u16,
|
|
193
|
+
pub(super) struct Decision {
|
|
194
|
+
pub(super) verdict: i32,
|
|
195
|
+
pub(super) chain: String,
|
|
196
|
+
pub(super) zone: u16,
|
|
188
197
|
}
|
|
189
198
|
/// The compiled table: rules per chain and element keys per set.
|
|
190
199
|
struct Table {
|
|
191
200
|
chains: BTreeMap<String, Vec<Vec<Attr>>>,
|
|
192
201
|
sets: BTreeMap<String, Vec<Vec<u8>>>,
|
|
193
202
|
}
|
|
194
|
-
fn chains(prepared: &Prepared) -> BTreeMap<String, Vec<Vec<Attr>>> {
|
|
203
|
+
pub(super) fn chains(prepared: &Prepared) -> BTreeMap<String, Vec<Vec<Attr>>> {
|
|
195
204
|
table(prepared).chains
|
|
196
205
|
}
|
|
197
206
|
fn table(prepared: &Prepared) -> Table {
|
|
@@ -246,6 +255,7 @@ fn load(flow: &Flow, name: &str, fields: &[Attr]) -> Option<Vec<u8>> {
|
|
|
246
255
|
Some(match name {
|
|
247
256
|
"meta" => match wire::number(fields, 2).unwrap() {
|
|
248
257
|
4 => interface(flow.input, false),
|
|
258
|
+
10 => flow.uid?.to_ne_bytes().to_vec(),
|
|
249
259
|
5 => interface(flow.output, false),
|
|
250
260
|
6 => interface(flow.input, true),
|
|
251
261
|
7 => interface(flow.output, true),
|
|
@@ -340,10 +350,14 @@ fn run(table: &Table, chain: &str, flow: &Flow, zone: &mut u16) -> Step {
|
|
|
340
350
|
}
|
|
341
351
|
}
|
|
342
352
|
"cmp" => {
|
|
343
|
-
assert_eq!(wire::number(&fields, 2).unwrap(), 0);
|
|
344
353
|
let expected = data(&fields, 3);
|
|
345
354
|
let at = offset(&fields, 1);
|
|
346
|
-
|
|
355
|
+
let equal = registers[at..at + expected.len()] == expected[..];
|
|
356
|
+
matched = match wire::number(&fields, 2).unwrap() {
|
|
357
|
+
0 => equal,
|
|
358
|
+
1 => !equal,
|
|
359
|
+
operation => panic!("cmp {operation}"),
|
|
360
|
+
};
|
|
347
361
|
}
|
|
348
362
|
"range" => {
|
|
349
363
|
assert_eq!(wire::number(&fields, 2).unwrap(), 0);
|
|
@@ -381,6 +395,10 @@ fn run(table: &Table, chain: &str, flow: &Flow, zone: &mut u16) -> Step {
|
|
|
381
395
|
registers[at..at + value.len()].copy_from_slice(&value);
|
|
382
396
|
}
|
|
383
397
|
"nat" => {}
|
|
398
|
+
"reject" => {
|
|
399
|
+
assert_eq!(fields, vec![Attr::u32(1, 1)]);
|
|
400
|
+
verdict = Some((REJECT, None));
|
|
401
|
+
}
|
|
384
402
|
other => panic!("{other}"),
|
|
385
403
|
}
|
|
386
404
|
if !matched {
|
|
@@ -391,7 +409,7 @@ fn run(table: &Table, chain: &str, flow: &Flow, zone: &mut u16) -> Step {
|
|
|
391
409
|
continue;
|
|
392
410
|
}
|
|
393
411
|
match verdict {
|
|
394
|
-
Some((code @ (0 | 1), _)) => {
|
|
412
|
+
Some((code @ (0 | 1 | REJECT), _)) => {
|
|
395
413
|
return Step::Final(Decision {
|
|
396
414
|
verdict: code,
|
|
397
415
|
chain: chain.into(),
|
|
@@ -412,7 +430,7 @@ fn run(table: &Table, chain: &str, flow: &Flow, zone: &mut u16) -> Step {
|
|
|
412
430
|
Step::Next
|
|
413
431
|
}
|
|
414
432
|
/// A base chain that ends without a terminal verdict accepts by policy.
|
|
415
|
-
fn decide(prepared: &Prepared, chain: &str, flow: &Flow) -> Decision {
|
|
433
|
+
pub(super) fn decide(prepared: &Prepared, chain: &str, flow: &Flow) -> Decision {
|
|
416
434
|
let mut zone = 0;
|
|
417
435
|
match run(&table(prepared), chain, flow, &mut zone) {
|
|
418
436
|
Step::Final(decision) => decision,
|
|
@@ -793,7 +811,7 @@ fn host_grants_reject_ranges_wildcards_and_unbound_addresses() {
|
|
|
793
811
|
}
|
|
794
812
|
|
|
795
813
|
/// `count` distinct exact grants to the fixture workload, both protocols.
|
|
796
|
-
fn many(count: usize) -> Value {
|
|
814
|
+
pub(super) fn many(count: usize) -> Value {
|
|
797
815
|
json!((0..count)
|
|
798
816
|
.map(|index| host_grant(
|
|
799
817
|
if index % 2 == 0 { "tcp" } else { "udp" },
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
//! Host-local platform endpoints in host transit: leased flows from a handoff
|
|
2
|
+
//! into a platform endpoint served on a host address, and nothing else.
|
|
3
|
+
use super::hostgrant_tests::{
|
|
4
|
+
bytes, chains, decide, normalized, opening, prepared, Flow, ACK, ESTABLISHED,
|
|
5
|
+
};
|
|
6
|
+
use super::tests::host;
|
|
7
|
+
use serde_json::{json, Value};
|
|
8
|
+
|
|
9
|
+
const TRANSIT: [u8; 4] = [10, 240, 0, 2];
|
|
10
|
+
const UPLINK: [u8; 4] = [192, 0, 2, 2];
|
|
11
|
+
const HANDOFF: [u8; 4] = [10, 240, 0, 1];
|
|
12
|
+
const LINK: Option<(u32, &str)> = Some((4, "handoff"));
|
|
13
|
+
|
|
14
|
+
/// The host fixture with two platform endpoints on host addresses: a TCP hub
|
|
15
|
+
/// on the uplink address and a UDP store on the handoff address.
|
|
16
|
+
pub(crate) fn served(mut value: Value) -> Value {
|
|
17
|
+
value["scope"]["protection"]["platformEndpoints"]
|
|
18
|
+
.as_array_mut()
|
|
19
|
+
.unwrap()
|
|
20
|
+
.extend([
|
|
21
|
+
json!({"id":"hub","address":"192.0.2.2","protocol":"tcp","port":8443}),
|
|
22
|
+
json!({"id":"store","address":"10.240.0.1","protocol":"udp","port":7000}),
|
|
23
|
+
]);
|
|
24
|
+
value
|
|
25
|
+
}
|
|
26
|
+
pub(crate) fn local(mut value: Value, ids: Value) -> Value {
|
|
27
|
+
value["scope"]["localPlatformEndpoints"] = ids;
|
|
28
|
+
value
|
|
29
|
+
}
|
|
30
|
+
fn exact() -> Value {
|
|
31
|
+
local(served(host()), json!(["store", "hub"]))
|
|
32
|
+
}
|
|
33
|
+
/// A leased opening from the transit source address arriving on the handoff.
|
|
34
|
+
fn arrival(protocol: u8, source_port: u16, destination: [u8; 4], port: u16) -> Flow {
|
|
35
|
+
Flow {
|
|
36
|
+
input: LINK,
|
|
37
|
+
output: None,
|
|
38
|
+
protocol,
|
|
39
|
+
source: TRANSIT,
|
|
40
|
+
destination,
|
|
41
|
+
source_port,
|
|
42
|
+
destination_port: port,
|
|
43
|
+
original: (TRANSIT, destination, source_port, port),
|
|
44
|
+
..opening(LINK, None)
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/// The host's answer to `flow`, leaving on the handoff.
|
|
48
|
+
fn answer(flow: Flow) -> Flow {
|
|
49
|
+
Flow {
|
|
50
|
+
input: None,
|
|
51
|
+
output: LINK,
|
|
52
|
+
source: flow.destination,
|
|
53
|
+
destination: flow.source,
|
|
54
|
+
source_port: flow.destination_port,
|
|
55
|
+
destination_port: flow.source_port,
|
|
56
|
+
tcp_flags: ACK,
|
|
57
|
+
state: ESTABLISHED,
|
|
58
|
+
reply: true,
|
|
59
|
+
..flow
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
#[test]
|
|
64
|
+
fn local_platform_endpoints_absent_and_empty_keep_the_host_digest_and_compiled_bytes() {
|
|
65
|
+
let baseline = prepared(host());
|
|
66
|
+
let empty = prepared(local(host(), json!([])));
|
|
67
|
+
assert_eq!(empty, baseline);
|
|
68
|
+
assert_eq!(bytes(&empty), bytes(&baseline));
|
|
69
|
+
let with = prepared(exact());
|
|
70
|
+
assert!(serde_json::to_string(&with.policy)
|
|
71
|
+
.unwrap()
|
|
72
|
+
.contains("\"localPlatformEndpoints\":[\"hub\",\"store\"]"));
|
|
73
|
+
// Canonical order.
|
|
74
|
+
assert_eq!(
|
|
75
|
+
prepared(local(served(host()), json!(["hub", "store"]))),
|
|
76
|
+
with
|
|
77
|
+
);
|
|
78
|
+
// The endpoints and the chains exist only when declared.
|
|
79
|
+
let graph = chains(&with);
|
|
80
|
+
assert!(
|
|
81
|
+
graph.contains_key("local_platform_in")
|
|
82
|
+
&& !chains(&baseline).contains_key("local_platform_in")
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
#[test]
|
|
87
|
+
fn local_platform_endpoint_on_a_host_address_is_refused_unless_declared() {
|
|
88
|
+
// The refusal stays for every undeclared endpoint on a host address.
|
|
89
|
+
assert!(normalized(served(host())).is_err());
|
|
90
|
+
assert!(normalized(local(served(host()), json!(["hub"]))).is_err());
|
|
91
|
+
assert!(normalized(local(served(host()), json!(["store"]))).is_err());
|
|
92
|
+
assert!(normalized(exact()).is_ok());
|
|
93
|
+
for invalid in [
|
|
94
|
+
// Unknown, duplicate, or an endpoint the host does not hold.
|
|
95
|
+
json!(["hub", "store", "missing"]),
|
|
96
|
+
json!(["hub", "store", "hub"]),
|
|
97
|
+
json!(["hub", "store", "dns"]),
|
|
98
|
+
json!(["hub", "store", "relay"]),
|
|
99
|
+
json!(["hub", "store", ""]),
|
|
100
|
+
json!(["hub", "store", 1]),
|
|
101
|
+
json!("hub"),
|
|
102
|
+
json!({"hub": true}),
|
|
103
|
+
] {
|
|
104
|
+
assert!(
|
|
105
|
+
normalized(local(served(host()), invalid.clone())).is_err(),
|
|
106
|
+
"{invalid}"
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
#[test]
|
|
112
|
+
fn local_platform_endpoint_admits_only_leased_exact_untranslated_flows_and_their_replies() {
|
|
113
|
+
let baseline = prepared(host());
|
|
114
|
+
let with = prepared(exact());
|
|
115
|
+
let tcp = arrival(6, 10500, UPLINK, 8443);
|
|
116
|
+
let udp = arrival(17, 10500, HANDOFF, 7000);
|
|
117
|
+
let decided = |flow: &Flow| {
|
|
118
|
+
let chain = if flow.input.is_some() {
|
|
119
|
+
"input"
|
|
120
|
+
} else {
|
|
121
|
+
"output"
|
|
122
|
+
};
|
|
123
|
+
(
|
|
124
|
+
decide(&baseline, chain, flow).verdict,
|
|
125
|
+
decide(&with, chain, flow).verdict,
|
|
126
|
+
)
|
|
127
|
+
};
|
|
128
|
+
// Every admitted packet: the TCP opening, established TCP, the UDP opening,
|
|
129
|
+
// and both replies. The baseline denies each at the handoff.
|
|
130
|
+
for flow in [
|
|
131
|
+
tcp,
|
|
132
|
+
Flow {
|
|
133
|
+
state: ESTABLISHED,
|
|
134
|
+
tcp_flags: ACK,
|
|
135
|
+
..tcp
|
|
136
|
+
},
|
|
137
|
+
udp,
|
|
138
|
+
answer(tcp),
|
|
139
|
+
answer(udp),
|
|
140
|
+
] {
|
|
141
|
+
assert_eq!(decided(&flow), (0, 1));
|
|
142
|
+
}
|
|
143
|
+
// Everything else keeps the baseline decision: a source port outside the
|
|
144
|
+
// leased range of the protocol, another source address, another port or
|
|
145
|
+
// protocol, a flow translated in transit, another zone, a TCP opening
|
|
146
|
+
// without SYN, and a host-origin opening toward the transit address.
|
|
147
|
+
for flow in [
|
|
148
|
+
arrival(6, 20000, UPLINK, 8443),
|
|
149
|
+
arrival(6, 9999, UPLINK, 8443),
|
|
150
|
+
arrival(6, 10500, UPLINK, 8444),
|
|
151
|
+
arrival(17, 10500, UPLINK, 8443),
|
|
152
|
+
arrival(6, 10500, HANDOFF, 7000),
|
|
153
|
+
arrival(17, 10500, HANDOFF, 7001),
|
|
154
|
+
Flow {
|
|
155
|
+
source: [10, 240, 0, 3],
|
|
156
|
+
original: ([10, 240, 0, 3], UPLINK, 10500, 8443),
|
|
157
|
+
..tcp
|
|
158
|
+
},
|
|
159
|
+
Flow {
|
|
160
|
+
original: (TRANSIT, HANDOFF, 10500, 9000),
|
|
161
|
+
..tcp
|
|
162
|
+
},
|
|
163
|
+
Flow { zone: 7, ..tcp },
|
|
164
|
+
Flow {
|
|
165
|
+
tcp_flags: ACK,
|
|
166
|
+
..tcp
|
|
167
|
+
},
|
|
168
|
+
Flow {
|
|
169
|
+
reply: false,
|
|
170
|
+
state: 8,
|
|
171
|
+
tcp_flags: 0x02,
|
|
172
|
+
original: (UPLINK, TRANSIT, 8443, 10500),
|
|
173
|
+
..answer(tcp)
|
|
174
|
+
},
|
|
175
|
+
Flow {
|
|
176
|
+
state: 8,
|
|
177
|
+
..answer(tcp)
|
|
178
|
+
},
|
|
179
|
+
] {
|
|
180
|
+
let (before, after) = decided(&flow);
|
|
181
|
+
assert_eq!(after, before, "{:?}", flow.original);
|
|
182
|
+
}
|
|
183
|
+
// A translated arrival that the source range admits is still refused: the
|
|
184
|
+
// original tuple is part of every check.
|
|
185
|
+
assert_eq!(
|
|
186
|
+
decided(&Flow {
|
|
187
|
+
original: (TRANSIT, HANDOFF, 10500, 9000),
|
|
188
|
+
..tcp
|
|
189
|
+
}),
|
|
190
|
+
(0, 0)
|
|
191
|
+
);
|
|
192
|
+
// Forwarded traffic and the uplink keep their exact previous rules. (The
|
|
193
|
+
// protected chains list every platform endpoint, served here or not.)
|
|
194
|
+
let graph = chains(&with);
|
|
195
|
+
let reference = chains(&baseline);
|
|
196
|
+
for name in ["forward", "post"] {
|
|
197
|
+
assert_eq!(graph[name], reference[name], "{name}");
|
|
198
|
+
}
|
|
199
|
+
// One jump per leased range per direction, ahead of the handoff denials.
|
|
200
|
+
assert_eq!(graph["input"].len(), reference["input"].len() + 2);
|
|
201
|
+
assert_eq!(graph["output"].len(), reference["output"].len() + 2);
|
|
202
|
+
assert_eq!(graph["local_platform_in"].len(), 4);
|
|
203
|
+
assert_eq!(graph["local_platform_back"].len(), 2);
|
|
204
|
+
}
|