@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,278 @@
|
|
|
1
|
+
use super::egress_traffic_tests::{
|
|
2
|
+
allocation, binding, connect, denied, ns_ip, protection, roundtrip,
|
|
3
|
+
};
|
|
4
|
+
use super::hostgrant_traffic_tests::Hop;
|
|
5
|
+
use super::router_traffic_tests::connect_from;
|
|
6
|
+
use super::*;
|
|
7
|
+
use crate::egress;
|
|
8
|
+
use serde_json::{json, Value};
|
|
9
|
+
use std::io::{Read, Write};
|
|
10
|
+
use std::net::{TcpListener, TcpStream, UdpSocket};
|
|
11
|
+
use std::time::Duration;
|
|
12
|
+
|
|
13
|
+
const A: &str = "wg_a";
|
|
14
|
+
const B: &str = "wg_b";
|
|
15
|
+
const C: &str = "wg_c";
|
|
16
|
+
const ROUTER: &str = "wg_router";
|
|
17
|
+
const HOST: &str = "wg_host";
|
|
18
|
+
|
|
19
|
+
fn endpoint(name: &str, prefix: &str, link: &Value) -> Value {
|
|
20
|
+
json!({"id":name,"interfaceIndex":link["interfaceIndex"],"interfaceName":name,
|
|
21
|
+
"interfaceKind":"veth","sourcePrefixes":[prefix]})
|
|
22
|
+
}
|
|
23
|
+
/// Three workloads behind the router, each with leased public egress on every
|
|
24
|
+
/// port of both protocols, exactly as a node composes it: a leased classifier
|
|
25
|
+
/// would claim a workload's packets if grants were not default-zone flows.
|
|
26
|
+
fn router_policy(revision: u64, granted: bool) -> egress::Policy {
|
|
27
|
+
let links: Vec<_> = [
|
|
28
|
+
("wka", "10.241.0.1"),
|
|
29
|
+
("wkb", "10.241.1.1"),
|
|
30
|
+
("wkc", "10.241.2.1"),
|
|
31
|
+
]
|
|
32
|
+
.into_iter()
|
|
33
|
+
.map(|(name, address)| binding(name, address, "veth"))
|
|
34
|
+
.collect();
|
|
35
|
+
let mut generation = allocation();
|
|
36
|
+
generation["conntrackZone"] = json!(17);
|
|
37
|
+
generation["conntrackLabel"] = json!("a".repeat(32));
|
|
38
|
+
let mut grants = Vec::new();
|
|
39
|
+
for (name, source) in [
|
|
40
|
+
("wka", "10.241.0.2"),
|
|
41
|
+
("wkb", "10.241.1.2"),
|
|
42
|
+
("wkc", "10.241.2.2"),
|
|
43
|
+
] {
|
|
44
|
+
for (protocol, first, last) in [("udp", 10000, 10015), ("tcp", 10016, 10031)] {
|
|
45
|
+
grants.push(json!({"sourceEndpoint":name,"sourcePrefix":format!("{source}/32"),
|
|
46
|
+
"destination":{"kind":"public","prefix":"0.0.0.0/0"},"protocol":protocol,
|
|
47
|
+
"sourcePort":null,"destinationPort":null,"sourcePortRange":{"first":first,"last":last}}));
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
generation["grants"] = json!(grants);
|
|
51
|
+
let workload_grants = if granted {
|
|
52
|
+
json!([
|
|
53
|
+
{"protocol":"tcp","sourceAddress":"10.241.0.2","destinationAddress":"10.241.1.2","destinationPort":8080},
|
|
54
|
+
{"protocol":"udp","sourceAddress":"10.241.0.2","destinationAddress":"10.241.1.2","destinationPort":5353}
|
|
55
|
+
])
|
|
56
|
+
} else {
|
|
57
|
+
json!([])
|
|
58
|
+
};
|
|
59
|
+
serde_json::from_value(json!({"schemaVersion":2,"revision":revision,"scope":{"kind":"routerEgress",
|
|
60
|
+
"endpoints":[endpoint("wka","10.241.0.2/32",&links[0]),endpoint("wkb","10.241.1.2/32",&links[1]),
|
|
61
|
+
endpoint("wkc","10.241.2.2/32",&links[2])],
|
|
62
|
+
"rules":[],"links":links,"handoff":binding("handoff","10.240.0.2","veth"),
|
|
63
|
+
"protection":protection(),"generations":[generation],"workloadGrants":workload_grants}}))
|
|
64
|
+
.unwrap()
|
|
65
|
+
}
|
|
66
|
+
fn udp(namespace: &str, address: &str) -> UdpSocket {
|
|
67
|
+
let address = address.to_owned();
|
|
68
|
+
let socket = in_namespace(namespace, move || UdpSocket::bind(address).unwrap());
|
|
69
|
+
socket
|
|
70
|
+
.set_read_timeout(Some(Duration::from_millis(250)))
|
|
71
|
+
.unwrap();
|
|
72
|
+
socket
|
|
73
|
+
}
|
|
74
|
+
/// A TCP flow from an exact source; on success the listener observes it and
|
|
75
|
+
/// data crosses both ways.
|
|
76
|
+
fn dial(
|
|
77
|
+
namespace: &str,
|
|
78
|
+
source: &str,
|
|
79
|
+
destination: &str,
|
|
80
|
+
listener: &TcpListener,
|
|
81
|
+
) -> Option<(TcpStream, TcpStream)> {
|
|
82
|
+
let (from, to) = (source.to_owned(), destination.to_owned());
|
|
83
|
+
let client = in_namespace(namespace, move || connect_from(&from, &to)).ok()?;
|
|
84
|
+
let (server, peer) = listener.accept().unwrap();
|
|
85
|
+
assert_eq!(peer.to_string(), source);
|
|
86
|
+
let (mut client, mut server) = (client, server);
|
|
87
|
+
for stream in [&client, &server] {
|
|
88
|
+
stream
|
|
89
|
+
.set_read_timeout(Some(Duration::from_secs(1)))
|
|
90
|
+
.unwrap();
|
|
91
|
+
}
|
|
92
|
+
client.write_all(b"ping").unwrap();
|
|
93
|
+
server.read_exact(&mut [0; 4]).unwrap();
|
|
94
|
+
server.write_all(b"pong").unwrap();
|
|
95
|
+
client.read_exact(&mut [0; 4]).unwrap();
|
|
96
|
+
Some((client, server))
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
#[test]
|
|
100
|
+
#[ignore = "requires disposable isolated native qualification guest"]
|
|
101
|
+
fn v2_workload_grant_opens_one_way_between_workloads_and_never_in_reverse() {
|
|
102
|
+
isolated();
|
|
103
|
+
for name in [A, B, C, ROUTER, HOST] {
|
|
104
|
+
ip(&["netns", "add", name]);
|
|
105
|
+
ns_ip(name, &["link", "set", "lo", "up"]);
|
|
106
|
+
}
|
|
107
|
+
in_namespace(ROUTER, || {
|
|
108
|
+
std::fs::write("/proc/sys/net/ipv4/ip_forward", "1").unwrap()
|
|
109
|
+
});
|
|
110
|
+
for (namespace, name, octet) in [(A, "wka", 0), (B, "wkb", 1), (C, "wkc", 2)] {
|
|
111
|
+
connect(
|
|
112
|
+
namespace,
|
|
113
|
+
"eth0",
|
|
114
|
+
&format!("10.241.{octet}.2/24"),
|
|
115
|
+
ROUTER,
|
|
116
|
+
name,
|
|
117
|
+
&format!("10.241.{octet}.1/24"),
|
|
118
|
+
);
|
|
119
|
+
ns_ip(
|
|
120
|
+
namespace,
|
|
121
|
+
&[
|
|
122
|
+
"route",
|
|
123
|
+
"add",
|
|
124
|
+
"default",
|
|
125
|
+
"via",
|
|
126
|
+
&format!("10.241.{octet}.1"),
|
|
127
|
+
],
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
connect(
|
|
131
|
+
ROUTER,
|
|
132
|
+
"handoff",
|
|
133
|
+
"10.240.0.2/30",
|
|
134
|
+
HOST,
|
|
135
|
+
"router",
|
|
136
|
+
"10.240.0.1/30",
|
|
137
|
+
);
|
|
138
|
+
let granted = in_namespace(B, || TcpListener::bind("10.241.1.2:8080").unwrap());
|
|
139
|
+
let other = in_namespace(B, || TcpListener::bind("10.241.1.2:8081").unwrap());
|
|
140
|
+
let third = in_namespace(C, || TcpListener::bind("10.241.2.2:8080").unwrap());
|
|
141
|
+
let source = in_namespace(A, || TcpListener::bind("10.241.0.2:9000").unwrap());
|
|
142
|
+
let served = udp(B, "10.241.1.2:5353");
|
|
143
|
+
let other_served = udp(B, "10.241.1.2:5354");
|
|
144
|
+
// Each phase uses its own client ports; TCP ports that closed stay in TIME_WAIT
|
|
145
|
+
// and an unreplied UDP entry would carry a decision over a policy change.
|
|
146
|
+
let exact = |phase: u16| {
|
|
147
|
+
let result = dial(
|
|
148
|
+
A,
|
|
149
|
+
&format!("10.241.0.2:{}", 40000 + phase),
|
|
150
|
+
"10.241.1.2:8080",
|
|
151
|
+
&granted,
|
|
152
|
+
);
|
|
153
|
+
let client = udp(A, &format!("10.241.0.2:{}", 41000 + phase));
|
|
154
|
+
(result, client)
|
|
155
|
+
};
|
|
156
|
+
let refused = |phase: u16| {
|
|
157
|
+
// Another port, another source workload, another destination workload.
|
|
158
|
+
assert!(dial(
|
|
159
|
+
A,
|
|
160
|
+
&format!("10.241.0.2:{}", 42000 + phase),
|
|
161
|
+
"10.241.1.2:8081",
|
|
162
|
+
&other
|
|
163
|
+
)
|
|
164
|
+
.is_none());
|
|
165
|
+
assert!(dial(
|
|
166
|
+
C,
|
|
167
|
+
&format!("10.241.2.2:{}", 40000 + phase),
|
|
168
|
+
"10.241.1.2:8080",
|
|
169
|
+
&granted
|
|
170
|
+
)
|
|
171
|
+
.is_none());
|
|
172
|
+
assert!(dial(
|
|
173
|
+
A,
|
|
174
|
+
&format!("10.241.0.2:{}", 43000 + phase),
|
|
175
|
+
"10.241.2.2:8080",
|
|
176
|
+
&third
|
|
177
|
+
)
|
|
178
|
+
.is_none());
|
|
179
|
+
// No reverse opening: the destination cannot open toward the source.
|
|
180
|
+
assert!(dial(
|
|
181
|
+
B,
|
|
182
|
+
&format!("10.241.1.2:{}", 44000 + phase),
|
|
183
|
+
"10.241.0.2:9000",
|
|
184
|
+
&source
|
|
185
|
+
)
|
|
186
|
+
.is_none());
|
|
187
|
+
let reverse = udp(B, &format!("10.241.1.2:{}", 45000 + phase));
|
|
188
|
+
denied(
|
|
189
|
+
&reverse,
|
|
190
|
+
&udp(A, &format!("10.241.0.2:{}", 46000 + phase)),
|
|
191
|
+
false,
|
|
192
|
+
);
|
|
193
|
+
denied(
|
|
194
|
+
&udp(A, &format!("10.241.0.2:{}", 47000 + phase)),
|
|
195
|
+
&other_served,
|
|
196
|
+
false,
|
|
197
|
+
);
|
|
198
|
+
};
|
|
199
|
+
// Positive controls without policy: every probe below can succeed.
|
|
200
|
+
let (flow, client) = exact(0);
|
|
201
|
+
assert!(flow.is_some());
|
|
202
|
+
roundtrip(&client, &served);
|
|
203
|
+
assert!(dial(A, "10.241.0.2:42000", "10.241.1.2:8081", &other).is_some());
|
|
204
|
+
assert!(dial(C, "10.241.2.2:40000", "10.241.1.2:8080", &granted).is_some());
|
|
205
|
+
assert!(dial(A, "10.241.0.2:43000", "10.241.2.2:8080", &third).is_some());
|
|
206
|
+
assert!(dial(B, "10.241.1.2:44000", "10.241.0.2:9000", &source).is_some());
|
|
207
|
+
roundtrip(&udp(B, "10.241.1.2:45000"), &udp(A, "10.241.0.2:46000"));
|
|
208
|
+
roundtrip(&udp(A, "10.241.0.2:47000"), &other_served);
|
|
209
|
+
|
|
210
|
+
let mut router = Hop::new(ROUTER, "workload_grant_router");
|
|
211
|
+
// Without grants the private endpoint denials refuse every workload flow.
|
|
212
|
+
router.apply(router_policy, 1, false);
|
|
213
|
+
let (flow, client) = exact(1);
|
|
214
|
+
assert!(flow.is_none());
|
|
215
|
+
denied(&client, &served, false);
|
|
216
|
+
refused(1);
|
|
217
|
+
// With them, exactly the source's openings and their answers pass.
|
|
218
|
+
router.apply(router_policy, 2, true);
|
|
219
|
+
let (flow, client) = exact(2);
|
|
220
|
+
let (mut established, mut accepted) = flow.expect("granted workload tcp");
|
|
221
|
+
let observed = roundtrip(&client, &served);
|
|
222
|
+
assert_eq!(observed.to_string(), "10.241.0.2:41002");
|
|
223
|
+
refused(2);
|
|
224
|
+
// Withdrawal stops new flows and the established one.
|
|
225
|
+
router.apply(router_policy, 3, false);
|
|
226
|
+
let (flow, client) = exact(3);
|
|
227
|
+
assert!(flow.is_none());
|
|
228
|
+
denied(&client, &served, false);
|
|
229
|
+
established.write_all(b"late").unwrap();
|
|
230
|
+
assert!(accepted.read_exact(&mut [0; 4]).is_err());
|
|
231
|
+
drop((established, accepted));
|
|
232
|
+
router.release();
|
|
233
|
+
// Release control.
|
|
234
|
+
let (flow, client) = exact(4);
|
|
235
|
+
assert!(flow.is_some());
|
|
236
|
+
roundtrip(&client, &served);
|
|
237
|
+
println!("V2_WORKLOAD_GRANT_PROOF positive_controls=true ungranted_dark=true exact_tcp=true exact_udp=true source_preserved=true leased_egress_unaffected=true other_port_denied=true other_source_denied=true other_destination_denied=true reverse_tcp_denied=true reverse_udp_denied=true withdrawn_dark=true established_withdrawn=true release_control=true");
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/// A full grant set of 1024 persists across owner loss, is re-verified element
|
|
241
|
+
/// by element before adoption, and is replaced and released like the rest.
|
|
242
|
+
#[test]
|
|
243
|
+
#[ignore = "requires disposable isolated native qualification guest"]
|
|
244
|
+
fn v2_workload_grant_set_persist_and_lost_ack_recovery() {
|
|
245
|
+
isolated();
|
|
246
|
+
// Peers live in another namespace, as the bound veth facts expect.
|
|
247
|
+
ip(&["netns", "add", HOST]);
|
|
248
|
+
for (name, address) in [
|
|
249
|
+
("wka", "10.241.0.1/24"),
|
|
250
|
+
("wkb", "10.241.1.1/24"),
|
|
251
|
+
("wkc", "10.241.2.1/24"),
|
|
252
|
+
("handoff", "10.240.0.2/30"),
|
|
253
|
+
] {
|
|
254
|
+
ip(&[
|
|
255
|
+
"link", "add", name, "type", "veth", "peer", "name", name, "netns", HOST,
|
|
256
|
+
]);
|
|
257
|
+
ip(&["address", "add", address, "dev", name]);
|
|
258
|
+
ip(&["link", "set", name, "up"]);
|
|
259
|
+
}
|
|
260
|
+
let mut policy = router_policy(1, true);
|
|
261
|
+
let egress::Scope::RouterEgress(scope) = &mut policy.scope else {
|
|
262
|
+
panic!()
|
|
263
|
+
};
|
|
264
|
+
scope.workload_grants = (0..1024_u16)
|
|
265
|
+
.map(|index| egress::WorkloadGrant {
|
|
266
|
+
protocol: if index % 2 == 0 { "tcp" } else { "udp" }.into(),
|
|
267
|
+
source_address: "10.241.0.2".into(),
|
|
268
|
+
destination_address: if index < 512 {
|
|
269
|
+
"10.241.1.2"
|
|
270
|
+
} else {
|
|
271
|
+
"10.241.2.2"
|
|
272
|
+
}
|
|
273
|
+
.into(),
|
|
274
|
+
destination_port: 20000 + index,
|
|
275
|
+
})
|
|
276
|
+
.collect();
|
|
277
|
+
super::egress_tests::recover("workload_grant_v2", policy);
|
|
278
|
+
}
|
package/ts/00_commitinfo_data.ts
CHANGED
|
@@ -88,6 +88,22 @@ export interface IManagedNftHostGrant {
|
|
|
88
88
|
destinationPort: number;
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
+
/** One stateful one-way flow between two workloads behind the same router: the source workload opens
|
|
92
|
+
* connections to one exact port of the destination workload, which may only answer. Both addresses are
|
|
93
|
+
* exact current addresses of two different veth workload endpoints of the scope. It admits the tuple
|
|
94
|
+
* (a TCP opening only with SYN) and its ESTABLISHED replies in the default conntrack zone and nothing
|
|
95
|
+
* else: no reverse opening, no translation and no source-port selection. Grant the reverse direction as
|
|
96
|
+
* a second grant. */
|
|
97
|
+
export interface IManagedNftWorkloadGrant {
|
|
98
|
+
protocol: 'tcp' | 'udp';
|
|
99
|
+
/** Exact unicast address of the dialling workload endpoint, never a prefix. */
|
|
100
|
+
sourceAddress: string;
|
|
101
|
+
/** Exact unicast address of the dialled workload endpoint, never a prefix. */
|
|
102
|
+
destinationAddress: string;
|
|
103
|
+
/** The destination workload's exact listening port, 1–65535; never a range. */
|
|
104
|
+
destinationPort: number;
|
|
105
|
+
}
|
|
106
|
+
|
|
91
107
|
/** One combined private/TUN/local/egress table; never compose a second ACCEPT over v1 terminal drops. */
|
|
92
108
|
export interface IManagedNftRouterEgressScopeV2 {
|
|
93
109
|
kind: 'routerEgress';
|
|
@@ -111,6 +127,10 @@ export interface IManagedNftRouterEgressScopeV2 {
|
|
|
111
127
|
* workload. Absent and empty are the same canonical policy, digest and compiled graph; at most 1024,
|
|
112
128
|
* held in a named set behind a constant number of rules. */
|
|
113
129
|
hostGrants?: IManagedNftHostGrant[];
|
|
130
|
+
/** Optional stateful one-way workload-to-workload grants. Absent and empty are the same canonical
|
|
131
|
+
* policy, digest and compiled graph; at most 1024, held in two named sets behind four rules. They
|
|
132
|
+
* share the scope's set element budget with `hostGrants`. */
|
|
133
|
+
workloadGrants?: IManagedNftWorkloadGrant[];
|
|
114
134
|
}
|
|
115
135
|
|
|
116
136
|
/** One inbound uplink publication, compiled inside the same host-transit generation. */
|
|
@@ -143,6 +163,28 @@ export interface IManagedNftHostTransitScopeV2 {
|
|
|
143
163
|
* host-local, a platform endpoint nor a leased transit source address. Absent and empty are the
|
|
144
164
|
* same canonical policy; at most 1024, held in a named set behind a constant number of rules. */
|
|
145
165
|
hostGrants?: IManagedNftHostGrant[];
|
|
166
|
+
/** Optional ids of `protection.platformEndpoints` this host serves itself, on an exact current address
|
|
167
|
+
* of the uplink or of a handoff link (rtnetlink verifies it at apply, recovery and inspection). Only a
|
|
168
|
+
* declared endpoint may hold a host address. Leased flows reach it from their exact handoff with the
|
|
169
|
+
* leased source address and a source port of the allocation's range for the endpoint's protocol; its
|
|
170
|
+
* ESTABLISHED replies return. Absent and empty are the same canonical policy, digest and compiled graph. */
|
|
171
|
+
localPlatformEndpoints?: string[];
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** One loopback TCP service that only one local user may dial. Every packet the host sends to the
|
|
175
|
+
* exact address and port from a socket of any other user is rejected with a TCP reset, after ordinary
|
|
176
|
+
* output destination NAT; the address and port arriving on any interface but loopback are dropped.
|
|
177
|
+
* The service must bind exactly this address: a wildcard listener stays reachable through others. */
|
|
178
|
+
export interface IManagedNftLocalTcpPortOwner {
|
|
179
|
+
/** Exact IPv4 loopback host address in 127.0.0.0/8, e.g. `127.0.0.1`. Never a prefix or wildcard. */
|
|
180
|
+
address: string;
|
|
181
|
+
/** The service's exact listening port, 1–65535; never a range. */
|
|
182
|
+
port: number;
|
|
183
|
+
/** The only user id whose sockets may send to the port: the socket file's file-system uid as
|
|
184
|
+
* the user namespace owning the network namespace sees it, 0–4294967294. Packets without a socket file carry no uid and
|
|
185
|
+
* are not matched: kernel replies, the teardown of an orphaned socket, and in-kernel sockets such as NFS or CIFS clients,
|
|
186
|
+
* which only privileged mounts create. */
|
|
187
|
+
uid: number;
|
|
146
188
|
}
|
|
147
189
|
|
|
148
190
|
/** Host-wide IPv4 destination denial for caller-authenticated private allocation pools.
|
|
@@ -156,6 +198,10 @@ export interface IManagedNftAllocationPoolGuardScopeV2 {
|
|
|
156
198
|
* listed pool, and their replies. Absent and empty are the same canonical policy; at most 1024,
|
|
157
199
|
* held in a named set behind a constant number of rules. */
|
|
158
200
|
hostGrants?: IManagedNftHostGrant[];
|
|
201
|
+
/** Optional host-wide loopback port ownership, compiled ahead of the guard in the same table.
|
|
202
|
+
* Absent and empty are the same canonical policy, digest and compiled graph; at most 8, one per
|
|
203
|
+
* exact address and port. */
|
|
204
|
+
localTcpPortOwners?: IManagedNftLocalTcpPortOwner[];
|
|
159
205
|
}
|
|
160
206
|
|
|
161
207
|
export interface IManagedNftPolicyV2 {
|