@push.rocks/smartnftables 1.6.0 → 1.8.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 +12 -0
- package/dist_rust/smartnftables_linux_amd64_musl +0 -0
- package/dist_rust/smartnftables_linux_amd64_musl.tsrust-build.json +4 -4
- package/dist_rust/smartnftables_linux_arm64_musl +0 -0
- package/dist_rust/smartnftables_linux_arm64_musl.tsrust-build.json +4 -4
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/classes.managednftables.d.ts +8 -1
- package/dist_ts/classes.managednftables.js +42 -6
- package/dist_ts/managed.egress.types.d.ts +9 -1
- package/dist_ts/managed.types.d.ts +7 -1
- package/package.json +1 -1
- package/readme.md +59 -2
- package/rust/src/egress.compile.rs +3 -0
- package/rust/src/egress.poolguard.rs +30 -0
- package/rust/src/egress.rs +27 -0
- package/rust/src/egress_tests.rs +85 -0
- package/rust/src/main.rs +4 -0
- package/rust/src/managed.rs +4 -0
- package/rust/src/owner.detach.rs +73 -0
- package/rust/src/owner.rs +20 -1
- package/rust/src/owner_detach_tests.rs +252 -0
- package/rust/src/owner_egress_tests.rs +1 -1
- package/rust/src/owner_egress_traffic_tests.rs +21 -10
- package/rust/src/owner_host_traffic_tests.rs +4 -10
- package/rust/src/owner_namespace_tests.rs +4 -4
- package/rust/src/owner_poolguard_tests.rs +268 -0
- package/rust/src/owner_tests.rs +6 -0
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.managednftables.ts +35 -5
- package/ts/managed.egress.types.ts +10 -1
- package/ts/managed.types.ts +2 -1
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
use super::*;
|
|
2
|
+
|
|
3
|
+
impl Owner {
|
|
4
|
+
/// Terminal retention, never adoption/deletion. Exact retry may observe an
|
|
5
|
+
/// orphan after an ACK was lost, including through a fresh native process.
|
|
6
|
+
pub fn detach(&mut self, expected: Applied) -> Result<()> {
|
|
7
|
+
self.validate_receipt(&expected)?;
|
|
8
|
+
if self.released || self.pending.is_some() {
|
|
9
|
+
return Err(Error::Conflict);
|
|
10
|
+
}
|
|
11
|
+
if let Some(retention) = &self.retention {
|
|
12
|
+
if retention.expected != expected {
|
|
13
|
+
return Err(Error::Conflict);
|
|
14
|
+
}
|
|
15
|
+
} else {
|
|
16
|
+
self.retention = Some(Retention {
|
|
17
|
+
expected: expected.clone(),
|
|
18
|
+
confirmed: false,
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
let result = (|| {
|
|
22
|
+
self.verify_retention(&expected, false)?;
|
|
23
|
+
// OWNER disappears with this socket; PERSIST must retain exactly
|
|
24
|
+
// the same table. The fresh verification socket never adopts it.
|
|
25
|
+
self.socket = None;
|
|
26
|
+
self.verify_retention(&expected, true)?;
|
|
27
|
+
self.socket = None;
|
|
28
|
+
Ok(())
|
|
29
|
+
})();
|
|
30
|
+
self.retention.as_mut().unwrap().confirmed = result.is_ok();
|
|
31
|
+
match result {
|
|
32
|
+
Ok(()) => {
|
|
33
|
+
self.applied = Some(expected);
|
|
34
|
+
self.error = None;
|
|
35
|
+
Ok(())
|
|
36
|
+
}
|
|
37
|
+
Err(error) => {
|
|
38
|
+
self.error = Some(error);
|
|
39
|
+
Err(error)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
fn verify_retention(&mut self, expected: &Applied, orphan_only: bool) -> Result<()> {
|
|
45
|
+
let graph = self.graph()?.ok_or(Error::Conflict)?;
|
|
46
|
+
self.binding(&graph, Some(expected))?;
|
|
47
|
+
let flags = wire::number(&graph.table, 2)?;
|
|
48
|
+
if (orphan_only && flags != 4)
|
|
49
|
+
|| (flags == 6 && wire::number(&graph.table, 7)? != self.socket()?.port)
|
|
50
|
+
|| !self.matches(&graph, &expected.prepared)?
|
|
51
|
+
{
|
|
52
|
+
return Err(Error::Conflict);
|
|
53
|
+
}
|
|
54
|
+
expected.prepared.validate_interfaces()
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
pub(super) fn inspect_retained(&mut self) -> Status {
|
|
58
|
+
let expected = self.retention.as_ref().unwrap().expected.clone();
|
|
59
|
+
if let Err(error) = self.verify_retention(&expected, true) {
|
|
60
|
+
self.retention.as_mut().unwrap().confirmed = false;
|
|
61
|
+
self.error = Some(error);
|
|
62
|
+
}
|
|
63
|
+
let enforced = self.retention.as_ref().unwrap().confirmed && self.error.is_none();
|
|
64
|
+
Status {
|
|
65
|
+
identity: self.identity.clone(),
|
|
66
|
+
state: if enforced { "retained" } else { "failed-owned" }.into(),
|
|
67
|
+
enforced,
|
|
68
|
+
applied: self.applied.clone(),
|
|
69
|
+
pending: self.pending.clone(),
|
|
70
|
+
error: self.error.map(|error| error.code().into()),
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
package/rust/src/owner.rs
CHANGED
|
@@ -68,8 +68,17 @@ pub struct Owner {
|
|
|
68
68
|
pending: Option<Transition>,
|
|
69
69
|
error: Option<Error>,
|
|
70
70
|
released: bool,
|
|
71
|
+
retention: Option<Retention>,
|
|
71
72
|
}
|
|
72
73
|
|
|
74
|
+
struct Retention {
|
|
75
|
+
expected: Applied,
|
|
76
|
+
confirmed: bool,
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
#[path = "owner.detach.rs"]
|
|
80
|
+
mod detach;
|
|
81
|
+
|
|
73
82
|
pub(crate) fn context() -> Result<(String, String, String)> {
|
|
74
83
|
let boot = std::fs::read_to_string("/proc/sys/kernel/random/boot_id")
|
|
75
84
|
.map_err(|_| Error::Unavailable)?
|
|
@@ -154,6 +163,7 @@ impl Owner {
|
|
|
154
163
|
pending: None,
|
|
155
164
|
error: None,
|
|
156
165
|
released: false,
|
|
166
|
+
retention: None,
|
|
157
167
|
})
|
|
158
168
|
}
|
|
159
169
|
fn socket(&mut self) -> Result<&mut Socket> {
|
|
@@ -350,7 +360,7 @@ impl Owner {
|
|
|
350
360
|
})
|
|
351
361
|
}
|
|
352
362
|
pub fn reconcile(&mut self, transition: Transition) -> Result<Applied> {
|
|
353
|
-
if self.released {
|
|
363
|
+
if self.released || self.retention.is_some() {
|
|
354
364
|
return Err(Error::Conflict);
|
|
355
365
|
}
|
|
356
366
|
transition.target.validate()?;
|
|
@@ -453,6 +463,9 @@ impl Owner {
|
|
|
453
463
|
self.applied(&graph, transition.target.clone())
|
|
454
464
|
}
|
|
455
465
|
pub fn inspect(&mut self) -> Status {
|
|
466
|
+
if self.retention.is_some() {
|
|
467
|
+
return self.inspect_retained();
|
|
468
|
+
}
|
|
456
469
|
let mut enforced = false;
|
|
457
470
|
if let Some(applied) = self.applied.clone().filter(|_| !self.released) {
|
|
458
471
|
match self.graph().and_then(|graph| {
|
|
@@ -490,6 +503,9 @@ impl Owner {
|
|
|
490
503
|
}
|
|
491
504
|
}
|
|
492
505
|
pub fn release(&mut self, expected: Applied) -> Result<()> {
|
|
506
|
+
if self.retention.is_some() {
|
|
507
|
+
return Err(Error::Conflict);
|
|
508
|
+
}
|
|
493
509
|
self.validate_receipt(&expected)?;
|
|
494
510
|
if self.pending.is_some() {
|
|
495
511
|
return Err(Error::Conflict);
|
|
@@ -545,6 +561,9 @@ impl Owner {
|
|
|
545
561
|
}
|
|
546
562
|
}
|
|
547
563
|
pub fn close(&mut self) -> Result<()> {
|
|
564
|
+
if self.retention.is_some() {
|
|
565
|
+
return Err(Error::Conflict);
|
|
566
|
+
}
|
|
548
567
|
if self.released {
|
|
549
568
|
return Ok(());
|
|
550
569
|
}
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
use super::namespace_tests::NativeOwner;
|
|
2
|
+
use super::*;
|
|
3
|
+
use crate::egress;
|
|
4
|
+
use serde_json::json;
|
|
5
|
+
use std::io::Write;
|
|
6
|
+
use std::time::{Duration, Instant};
|
|
7
|
+
|
|
8
|
+
fn pool_target(revision: u64) -> Prepared {
|
|
9
|
+
let mut policy = egress::tests::pool_guard();
|
|
10
|
+
policy["revision"] = json!(revision);
|
|
11
|
+
serde_json::from_value::<egress::Policy>(policy)
|
|
12
|
+
.unwrap()
|
|
13
|
+
.prepare()
|
|
14
|
+
.unwrap()
|
|
15
|
+
.into()
|
|
16
|
+
}
|
|
17
|
+
fn apply(name: &str) -> (Owner, Applied) {
|
|
18
|
+
let mut owner = Owner::new(options(name)).unwrap();
|
|
19
|
+
let applied = owner
|
|
20
|
+
.reconcile(Transition {
|
|
21
|
+
previous: None,
|
|
22
|
+
target: pool_target(1),
|
|
23
|
+
})
|
|
24
|
+
.unwrap();
|
|
25
|
+
(owner, applied)
|
|
26
|
+
}
|
|
27
|
+
fn retained(owner: &mut Owner, applied: &Applied) {
|
|
28
|
+
let status = owner.inspect();
|
|
29
|
+
assert_eq!(status.state, "retained");
|
|
30
|
+
assert!(status.enforced);
|
|
31
|
+
assert_eq!(status.applied, Some(applied.clone()));
|
|
32
|
+
let graph = owner.graph().unwrap().unwrap();
|
|
33
|
+
assert_eq!(wire::number(&graph.table, 2).unwrap(), 4);
|
|
34
|
+
assert_eq!(
|
|
35
|
+
wire::handle(&graph.table, 4).unwrap().to_string(),
|
|
36
|
+
applied.receipt.table_handle
|
|
37
|
+
);
|
|
38
|
+
assert!(owner.matches(&graph, &applied.prepared).unwrap());
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
#[test]
|
|
42
|
+
#[ignore = "requires disposable isolated native qualification guest"]
|
|
43
|
+
fn detach_retains_exact_graph_is_terminal_and_replays_without_adoption() {
|
|
44
|
+
isolated();
|
|
45
|
+
let (mut owner, applied) = apply("detach_replay");
|
|
46
|
+
owner.detach(applied.clone()).unwrap();
|
|
47
|
+
retained(&mut owner, &applied);
|
|
48
|
+
owner.detach(applied.clone()).unwrap();
|
|
49
|
+
assert_eq!(owner.close(), Err(Error::Conflict));
|
|
50
|
+
assert_eq!(owner.release(applied.clone()), Err(Error::Conflict));
|
|
51
|
+
assert_eq!(
|
|
52
|
+
owner.reconcile(Transition {
|
|
53
|
+
previous: Some(applied.clone()),
|
|
54
|
+
target: pool_target(2)
|
|
55
|
+
}),
|
|
56
|
+
Err(Error::Conflict)
|
|
57
|
+
);
|
|
58
|
+
let mut different = applied.clone();
|
|
59
|
+
different.prepared = pool_target(2);
|
|
60
|
+
different.receipt.revision = 2;
|
|
61
|
+
different.receipt.digest = different.prepared.digest.clone();
|
|
62
|
+
assert_eq!(owner.detach(different), Err(Error::Conflict));
|
|
63
|
+
retained(&mut owner, &applied);
|
|
64
|
+
drop(owner);
|
|
65
|
+
let mut fresh = Owner::new(options("detach_replay")).unwrap();
|
|
66
|
+
fresh.detach(applied.clone()).unwrap();
|
|
67
|
+
retained(&mut fresh, &applied);
|
|
68
|
+
// A separate active owner invalidates retention inspection. It is never
|
|
69
|
+
// detached by this reader, even with the same full logical identity.
|
|
70
|
+
let mut active = Owner::new(options("detach_replay")).unwrap();
|
|
71
|
+
active
|
|
72
|
+
.reconcile(Transition {
|
|
73
|
+
previous: None,
|
|
74
|
+
target: applied.prepared.clone(),
|
|
75
|
+
})
|
|
76
|
+
.unwrap();
|
|
77
|
+
assert_eq!(fresh.detach(applied.clone()), Err(Error::Conflict));
|
|
78
|
+
assert!(!fresh.inspect().enforced);
|
|
79
|
+
assert!(active.inspect().enforced);
|
|
80
|
+
assert_eq!(fresh.close(), Err(Error::Conflict));
|
|
81
|
+
drop(active);
|
|
82
|
+
fresh.detach(applied.clone()).unwrap();
|
|
83
|
+
retained(&mut fresh, &applied);
|
|
84
|
+
drop(fresh);
|
|
85
|
+
Owner::new(options("detach_replay"))
|
|
86
|
+
.unwrap()
|
|
87
|
+
.release(applied)
|
|
88
|
+
.unwrap();
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
#[test]
|
|
92
|
+
#[ignore = "requires disposable isolated native qualification guest"]
|
|
93
|
+
fn detach_rejects_pending_wrong_receipts_foreign_mutation_and_absence() {
|
|
94
|
+
isolated();
|
|
95
|
+
let (mut owner, applied) = apply("detach_faults");
|
|
96
|
+
let pending = Transition {
|
|
97
|
+
previous: Some(applied.clone()),
|
|
98
|
+
target: pool_target(2),
|
|
99
|
+
};
|
|
100
|
+
owner.pending = Some(pending.clone());
|
|
101
|
+
assert_eq!(owner.detach(applied.clone()), Err(Error::Conflict));
|
|
102
|
+
assert!(owner.retention.is_none());
|
|
103
|
+
let applied = owner.reconcile(pending).unwrap();
|
|
104
|
+
for field in ["boot", "namespace", "handle", "digest"] {
|
|
105
|
+
let mut invalid = applied.clone();
|
|
106
|
+
match field {
|
|
107
|
+
"boot" => {
|
|
108
|
+
invalid.receipt.identity.boot_id = "00000000-0000-0000-0000-000000000000".into()
|
|
109
|
+
}
|
|
110
|
+
"namespace" => invalid.receipt.identity.namespace_inode = "0".into(),
|
|
111
|
+
"handle" => invalid.receipt.table_handle = "18446744073709551615".into(),
|
|
112
|
+
_ => invalid.receipt.digest = format!("sha256:{}", "0".repeat(64)),
|
|
113
|
+
}
|
|
114
|
+
let mut reader = Owner::new(options("detach_faults")).unwrap();
|
|
115
|
+
assert_eq!(reader.detach(invalid), Err(Error::Conflict));
|
|
116
|
+
assert!(owner.inspect().enforced);
|
|
117
|
+
}
|
|
118
|
+
owner.detach(applied.clone()).unwrap();
|
|
119
|
+
// Mutation after detachment is observed on exact retry and permanently
|
|
120
|
+
// retains the failed owner. No failure cleanup adopts or deletes the graph.
|
|
121
|
+
let mut foreign = Socket::open().unwrap();
|
|
122
|
+
let generation = foreign.generation().unwrap();
|
|
123
|
+
foreign
|
|
124
|
+
.batch(
|
|
125
|
+
generation,
|
|
126
|
+
vec![(
|
|
127
|
+
3,
|
|
128
|
+
0x600,
|
|
129
|
+
vec![
|
|
130
|
+
Attr::string(1, "snft_detach_faults"),
|
|
131
|
+
Attr::string(3, "foreign"),
|
|
132
|
+
],
|
|
133
|
+
)],
|
|
134
|
+
)
|
|
135
|
+
.unwrap();
|
|
136
|
+
assert_eq!(owner.detach(applied.clone()), Err(Error::Conflict));
|
|
137
|
+
assert!(!owner.inspect().enforced);
|
|
138
|
+
assert_eq!(owner.close(), Err(Error::Conflict));
|
|
139
|
+
assert_eq!(owner.release(applied.clone()), Err(Error::Conflict));
|
|
140
|
+
assert!(owner
|
|
141
|
+
.graph()
|
|
142
|
+
.unwrap()
|
|
143
|
+
.unwrap()
|
|
144
|
+
.chains
|
|
145
|
+
.iter()
|
|
146
|
+
.any(|chain| wire::text(chain, 3).unwrap() == "foreign"));
|
|
147
|
+
drop(owner);
|
|
148
|
+
// This disposable fixture's foreign owner deletes only its exact orphan.
|
|
149
|
+
let generation = foreign.generation().unwrap();
|
|
150
|
+
foreign
|
|
151
|
+
.batch(
|
|
152
|
+
generation,
|
|
153
|
+
vec![(2, 0, vec![Attr::string(1, "snft_detach_faults")])],
|
|
154
|
+
)
|
|
155
|
+
.unwrap();
|
|
156
|
+
let mut absent = Owner::new(options("detach_faults")).unwrap();
|
|
157
|
+
assert_eq!(absent.detach(applied), Err(Error::Conflict));
|
|
158
|
+
assert!(!absent.inspect().enforced);
|
|
159
|
+
assert_eq!(absent.close(), Err(Error::Conflict));
|
|
160
|
+
assert!(absent.graph().unwrap().is_none());
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
#[test]
|
|
164
|
+
#[ignore = "requires disposable isolated native qualification guest"]
|
|
165
|
+
fn detach_rechecks_interface_bindings_without_releasing_on_drift() {
|
|
166
|
+
isolated();
|
|
167
|
+
let endpoint = endpoint("a", 71);
|
|
168
|
+
let policy = crate::policy::Policy {
|
|
169
|
+
schema_version: 1,
|
|
170
|
+
revision: 1,
|
|
171
|
+
endpoints: vec![endpoint],
|
|
172
|
+
rules: vec![],
|
|
173
|
+
};
|
|
174
|
+
let mut owner = Owner::new(options("detach_links")).unwrap();
|
|
175
|
+
let applied = owner
|
|
176
|
+
.reconcile(Transition {
|
|
177
|
+
previous: None,
|
|
178
|
+
target: policy.prepare().unwrap().into(),
|
|
179
|
+
})
|
|
180
|
+
.unwrap();
|
|
181
|
+
ip(&["link", "set", "worka", "name", "renameda"]);
|
|
182
|
+
assert_eq!(owner.detach(applied.clone()), Err(Error::Conflict));
|
|
183
|
+
assert_eq!(owner.close(), Err(Error::Conflict));
|
|
184
|
+
let graph = owner.graph().unwrap().unwrap();
|
|
185
|
+
assert_eq!(
|
|
186
|
+
wire::handle(&graph.table, 4).unwrap().to_string(),
|
|
187
|
+
applied.receipt.table_handle
|
|
188
|
+
);
|
|
189
|
+
ip(&["link", "set", "renameda", "name", "worka"]);
|
|
190
|
+
owner.detach(applied.clone()).unwrap();
|
|
191
|
+
retained(&mut owner, &applied);
|
|
192
|
+
drop(owner);
|
|
193
|
+
Owner::new(options("detach_links"))
|
|
194
|
+
.unwrap()
|
|
195
|
+
.release(applied)
|
|
196
|
+
.unwrap();
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
#[test]
|
|
200
|
+
#[ignore = "requires disposable isolated native qualification guest"]
|
|
201
|
+
fn detach_recovers_lost_response_and_undelivered_request_across_real_native_processes() {
|
|
202
|
+
isolated();
|
|
203
|
+
let namespace = std::fs::File::open("/proc/thread-self/ns/net").unwrap();
|
|
204
|
+
for delivered in [true, false] {
|
|
205
|
+
let name = if delivered {
|
|
206
|
+
"detach_ack"
|
|
207
|
+
} else {
|
|
208
|
+
"detach_unsent"
|
|
209
|
+
};
|
|
210
|
+
let mut native = NativeOwner::start(&namespace);
|
|
211
|
+
native.call("openOwner", serde_json::to_value(options(name)).unwrap());
|
|
212
|
+
let applied: Applied = serde_json::from_value(native.call(
|
|
213
|
+
"reconcilePolicy",
|
|
214
|
+
json!({"previous":null,"target":pool_target(1)}),
|
|
215
|
+
))
|
|
216
|
+
.unwrap();
|
|
217
|
+
let mut reader = Owner::new(options(name)).unwrap();
|
|
218
|
+
reader.validate_receipt(&applied).unwrap();
|
|
219
|
+
if delivered {
|
|
220
|
+
writeln!(
|
|
221
|
+
native.child.stdin.as_mut().unwrap(),
|
|
222
|
+
"{}",
|
|
223
|
+
json!({"id":"lost-detach","method":"detachPolicy","params":applied})
|
|
224
|
+
)
|
|
225
|
+
.unwrap();
|
|
226
|
+
let until = Instant::now() + Duration::from_secs(3);
|
|
227
|
+
loop {
|
|
228
|
+
let graph = reader.graph().unwrap().unwrap();
|
|
229
|
+
if wire::number(&graph.table, 2).unwrap() == 4 {
|
|
230
|
+
break;
|
|
231
|
+
}
|
|
232
|
+
assert!(Instant::now() < until);
|
|
233
|
+
std::thread::sleep(Duration::from_millis(5));
|
|
234
|
+
}
|
|
235
|
+
// Do not read the detach response: terminate the actual process
|
|
236
|
+
// after observing its kernel effect but before recording an ACK.
|
|
237
|
+
}
|
|
238
|
+
drop(native);
|
|
239
|
+
let mut fresh = NativeOwner::start(&namespace);
|
|
240
|
+
fresh.call("openOwner", serde_json::to_value(options(name)).unwrap());
|
|
241
|
+
assert_eq!(
|
|
242
|
+
fresh.call("detachPolicy", serde_json::to_value(&applied).unwrap()),
|
|
243
|
+
json!({"detached":true})
|
|
244
|
+
);
|
|
245
|
+
assert_eq!(fresh.call("inspectPolicy", json!({}))["state"], "retained");
|
|
246
|
+
drop(fresh);
|
|
247
|
+
let graph = reader.graph().unwrap().unwrap();
|
|
248
|
+
assert_eq!(wire::number(&graph.table, 2).unwrap(), 4);
|
|
249
|
+
assert!(reader.matches(&graph, &applied.prepared).unwrap());
|
|
250
|
+
reader.release(applied).unwrap();
|
|
251
|
+
}
|
|
252
|
+
}
|
|
@@ -62,7 +62,7 @@ fn host() -> egress::Policy {
|
|
|
62
62
|
value["scope"]["uplink"] = binding("uplink", "192.0.2.2", "veth");
|
|
63
63
|
serde_json::from_value(value).unwrap()
|
|
64
64
|
}
|
|
65
|
-
fn recover(name: &str, policy: egress::Policy) {
|
|
65
|
+
pub(super) fn recover(name: &str, policy: egress::Policy) {
|
|
66
66
|
let prepared: Prepared = policy.clone().prepare().unwrap().into();
|
|
67
67
|
prepared.validate_interfaces().expect("fixture bindings");
|
|
68
68
|
let mut owner = Owner::new(options(name)).unwrap();
|
|
@@ -205,6 +205,18 @@ fn v2_four_namespace_private_tun_local_dns_egress_and_revocation() {
|
|
|
205
205
|
ns_ip(ROUTER, &["address", "add", "10.242.0.1/24", "dev", "vpn"]);
|
|
206
206
|
ns_ip(ROUTER, &["link", "set", "vpn", "up"]);
|
|
207
207
|
let mut device = in_namespace(ROUTER, tun);
|
|
208
|
+
let (pool_owner, pool_applied) = in_namespace(HOST, || {
|
|
209
|
+
let mut owner = Owner::new(options("pool_coexistence")).unwrap();
|
|
210
|
+
let policy: egress::Policy = serde_json::from_value(egress::tests::pool_guard()).unwrap();
|
|
211
|
+
let applied = owner
|
|
212
|
+
.reconcile(Transition {
|
|
213
|
+
previous: None,
|
|
214
|
+
target: policy.prepare().unwrap().into(),
|
|
215
|
+
})
|
|
216
|
+
.unwrap();
|
|
217
|
+
assert!(owner.inspect().enforced);
|
|
218
|
+
(owner, applied)
|
|
219
|
+
});
|
|
208
220
|
let (host_owner, host_applied) = in_namespace(HOST, || {
|
|
209
221
|
let mut owner = Owner::new(options("host_packets")).unwrap();
|
|
210
222
|
let applied = owner
|
|
@@ -260,11 +272,8 @@ fn v2_four_namespace_private_tun_local_dns_egress_and_revocation() {
|
|
|
260
272
|
});
|
|
261
273
|
let listener = in_namespace(PEER, || TcpListener::bind("192.0.2.3:3000").unwrap());
|
|
262
274
|
let mut client = in_namespace(WORK, || {
|
|
263
|
-
TcpStream::connect_timeout(
|
|
264
|
-
|
|
265
|
-
Duration::from_secs(1),
|
|
266
|
-
)
|
|
267
|
-
.unwrap()
|
|
275
|
+
TcpStream::connect_timeout(&"192.0.2.3:3000".parse().unwrap(), Duration::from_secs(1))
|
|
276
|
+
.unwrap()
|
|
268
277
|
});
|
|
269
278
|
let (mut server, peer) = listener.accept().unwrap();
|
|
270
279
|
assert_eq!(peer.ip().to_string(), "192.0.2.2");
|
|
@@ -290,11 +299,8 @@ fn v2_four_namespace_private_tun_local_dns_egress_and_revocation() {
|
|
|
290
299
|
assert_eq!(syn[33] & 0xc0, 0xc0);
|
|
291
300
|
assert!((10016..=10031).contains(&u16::from_be_bytes(syn[20..22].try_into().unwrap())));
|
|
292
301
|
let mut local_tcp = in_namespace(ROUTER, || {
|
|
293
|
-
TcpStream::connect_timeout(
|
|
294
|
-
|
|
295
|
-
Duration::from_secs(1),
|
|
296
|
-
)
|
|
297
|
-
.unwrap()
|
|
302
|
+
TcpStream::connect_timeout(&"192.0.2.3:3000".parse().unwrap(), Duration::from_secs(1))
|
|
303
|
+
.unwrap()
|
|
298
304
|
});
|
|
299
305
|
let (mut local_peer, local_outer) = listener.accept().unwrap();
|
|
300
306
|
assert!((10016..=10031).contains(&local_outer.port()));
|
|
@@ -466,5 +472,10 @@ fn v2_four_namespace_private_tun_local_dns_egress_and_revocation() {
|
|
|
466
472
|
let mut owner = host_owner;
|
|
467
473
|
owner.release(host_applied).unwrap();
|
|
468
474
|
});
|
|
475
|
+
in_namespace(HOST, move || {
|
|
476
|
+
let mut owner = pool_owner;
|
|
477
|
+
assert!(owner.inspect().enforced);
|
|
478
|
+
owner.release(pool_applied).unwrap();
|
|
479
|
+
});
|
|
469
480
|
println!("V2_PACKET_PROOF private_dns=true tun_private=true tun_public_denied=true workload_public=true local_dns_and_tcp=true handoff_udp_tcp_ranges=true ecn_syn=true opening_ack_rst_denied=true fragments_checked=true range_exhaustion_denied=true protected_grant_denied=true ungranted_local_denied=true retired_replies_denied=true revoked_egress_denied=true disjoint_generation_restored=true flows_drained=false reusable=false");
|
|
470
481
|
}
|
|
@@ -7,7 +7,7 @@ const SOURCE: &str = "hs_source";
|
|
|
7
7
|
const HOST: &str = "hs_host";
|
|
8
8
|
const PEER: &str = "hs_peer";
|
|
9
9
|
|
|
10
|
-
fn payload(offset: u32, bytes: Vec<u8>, transport: bool) -> Vec<Attr> {
|
|
10
|
+
pub(super) fn payload(offset: u32, bytes: Vec<u8>, transport: bool) -> Vec<Attr> {
|
|
11
11
|
vec![
|
|
12
12
|
expr(
|
|
13
13
|
"payload",
|
|
@@ -21,7 +21,7 @@ fn payload(offset: u32, bytes: Vec<u8>, transport: bool) -> Vec<Attr> {
|
|
|
21
21
|
compare(bytes),
|
|
22
22
|
]
|
|
23
23
|
}
|
|
24
|
-
fn immediate(value: Vec<u8>) -> Attr {
|
|
24
|
+
pub(super) fn immediate(value: Vec<u8>) -> Attr {
|
|
25
25
|
expr(
|
|
26
26
|
"immediate",
|
|
27
27
|
vec![
|
|
@@ -196,10 +196,7 @@ fn v2_host_rejects_foreign_dnat_zone_local_diversion_and_ipv6() {
|
|
|
196
196
|
HOST,
|
|
197
197
|
&["route", "add", "203.0.113.2/32", "via", "192.0.2.3"],
|
|
198
198
|
);
|
|
199
|
-
ns_ip(
|
|
200
|
-
HOST,
|
|
201
|
-
&["route", "add", "10.250.0.2/32", "via", "192.0.2.3"],
|
|
202
|
-
);
|
|
199
|
+
ns_ip(HOST, &["route", "add", "10.250.0.2/32", "via", "192.0.2.3"]);
|
|
203
200
|
for address in ["203.0.113.2/32", "10.250.0.2/32"] {
|
|
204
201
|
ns_ip(PEER, &["address", "add", address, "dev", "lo"]);
|
|
205
202
|
}
|
|
@@ -285,10 +282,7 @@ fn v2_host_rejects_foreign_dnat_zone_local_diversion_and_ipv6() {
|
|
|
285
282
|
control(source, destination, target, false);
|
|
286
283
|
}
|
|
287
284
|
let allowed = socket(SOURCE, "10.240.0.2:10000");
|
|
288
|
-
assert_eq!(
|
|
289
|
-
roundtrip(&allowed, &public).ip().to_string(),
|
|
290
|
-
"192.0.2.2"
|
|
291
|
-
);
|
|
285
|
+
assert_eq!(roundtrip(&allowed, &public).ip().to_string(), "192.0.2.2");
|
|
292
286
|
let platform = socket(PEER, "192.0.2.3:5300");
|
|
293
287
|
roundtrip(&allowed, &platform);
|
|
294
288
|
let unleased = socket(SOURCE, "10.240.0.2:9999");
|
|
@@ -30,13 +30,13 @@ fn command(namespace: &File) -> Command {
|
|
|
30
30
|
command
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
struct NativeOwner {
|
|
34
|
-
child: Child,
|
|
33
|
+
pub(super) struct NativeOwner {
|
|
34
|
+
pub(super) child: Child,
|
|
35
35
|
messages: Receiver<Value>,
|
|
36
36
|
reader: Option<JoinHandle<()>>,
|
|
37
37
|
}
|
|
38
38
|
impl NativeOwner {
|
|
39
|
-
fn start(namespace: &File) -> Self {
|
|
39
|
+
pub(super) fn start(namespace: &File) -> Self {
|
|
40
40
|
let mut child = command(namespace).spawn().unwrap();
|
|
41
41
|
let output = child.stdout.take().unwrap();
|
|
42
42
|
let (send, messages) = mpsc::channel();
|
|
@@ -76,7 +76,7 @@ impl NativeOwner {
|
|
|
76
76
|
assert_eq!(response["id"], method);
|
|
77
77
|
response
|
|
78
78
|
}
|
|
79
|
-
fn call(&mut self, method: &str, params: Value) -> Value {
|
|
79
|
+
pub(super) fn call(&mut self, method: &str, params: Value) -> Value {
|
|
80
80
|
let response = self.response(method, params);
|
|
81
81
|
assert_eq!(response["success"], true, "{response}");
|
|
82
82
|
response["result"].clone()
|