@push.rocks/smartnftables 1.3.1 → 1.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.
Files changed (43) hide show
  1. package/changelog.md +24 -0
  2. package/dist_rust/smartnftables_linux_amd64_musl +0 -0
  3. package/dist_rust/smartnftables_linux_amd64_musl.tsrust-build.json +4 -4
  4. package/dist_rust/smartnftables_linux_arm64_musl +0 -0
  5. package/dist_rust/smartnftables_linux_arm64_musl.tsrust-build.json +4 -4
  6. package/dist_ts/00_commitinfo_data.js +1 -1
  7. package/dist_ts/classes.managednftables.d.ts +7 -7
  8. package/dist_ts/classes.managednftables.js +9 -3
  9. package/dist_ts/index.d.ts +1 -0
  10. package/dist_ts/index.js +1 -1
  11. package/dist_ts/managed.egress.types.d.ts +91 -0
  12. package/dist_ts/managed.egress.types.js +2 -0
  13. package/dist_ts/managed.types.d.ts +25 -19
  14. package/package.json +5 -5
  15. package/readme.md +97 -3
  16. package/rust/src/egress.compile.rs +288 -0
  17. package/rust/src/egress.host.rs +103 -0
  18. package/rust/src/egress.router.rs +231 -0
  19. package/rust/src/egress.rs +682 -0
  20. package/rust/src/egress_tests.rs +332 -0
  21. package/rust/src/main.rs +23 -3
  22. package/rust/src/managed.rs +105 -0
  23. package/rust/src/namespace.rs +51 -0
  24. package/rust/src/owner.rs +96 -6
  25. package/rust/src/owner_coexistence_tests.rs +77 -0
  26. package/rust/src/owner_egress_identity_tests.rs +155 -0
  27. package/rust/src/owner_egress_tests.rs +153 -0
  28. package/rust/src/owner_egress_traffic_tests.rs +470 -0
  29. package/rust/src/owner_host_traffic_tests.rs +305 -0
  30. package/rust/src/owner_identity_tests.rs +169 -0
  31. package/rust/src/owner_link_tests.rs +119 -0
  32. package/rust/src/owner_namespace_tests.rs +242 -0
  33. package/rust/src/owner_packet_fixture.rs +200 -0
  34. package/rust/src/owner_tests.rs +36 -7
  35. package/rust/src/policy.rs +120 -81
  36. package/rust/src/tests.rs +22 -0
  37. package/rust/src/wire.links.rs +268 -0
  38. package/rust/src/wire.rs +3 -64
  39. package/ts/00_commitinfo_data.ts +1 -1
  40. package/ts/classes.managednftables.ts +19 -14
  41. package/ts/index.ts +1 -0
  42. package/ts/managed.egress.types.ts +85 -0
  43. package/ts/managed.types.ts +24 -16
@@ -0,0 +1,242 @@
1
+ use super::{empty, in_namespace, ip, isolated, options};
2
+ use crate::owner::{Applied, Owner, Transition};
3
+ use serde_json::{json, Value};
4
+ use std::fs::{self, File};
5
+ use std::io::{BufRead, BufReader, Read, Write};
6
+ use std::os::fd::AsRawFd;
7
+ use std::os::unix::fs::MetadataExt;
8
+ use std::os::unix::process::CommandExt;
9
+ use std::process::{Child, Command, Stdio};
10
+ use std::sync::mpsc::{self, Receiver};
11
+ use std::thread::{self, JoinHandle};
12
+ use std::time::{Duration, Instant};
13
+
14
+ fn command(namespace: &File) -> Command {
15
+ let fd = namespace.as_raw_fd();
16
+ let mut command = Command::new("/bin/managed");
17
+ command
18
+ .args(["--management", "--network-namespace-fd", "3"])
19
+ .stdin(Stdio::piped())
20
+ .stdout(Stdio::piped())
21
+ .stderr(Stdio::inherit());
22
+ unsafe {
23
+ command.pre_exec(move || {
24
+ if libc::dup2(fd, 3) < 0 || libc::fcntl(3, libc::F_SETFD, 0) < 0 {
25
+ return Err(std::io::Error::last_os_error());
26
+ }
27
+ Ok(())
28
+ });
29
+ }
30
+ command
31
+ }
32
+
33
+ struct NativeOwner {
34
+ child: Child,
35
+ messages: Receiver<Value>,
36
+ reader: Option<JoinHandle<()>>,
37
+ }
38
+ impl NativeOwner {
39
+ fn start(namespace: &File) -> Self {
40
+ let mut child = command(namespace).spawn().unwrap();
41
+ let output = child.stdout.take().unwrap();
42
+ let (send, messages) = mpsc::channel();
43
+ let reader = thread::spawn(move || {
44
+ for line in BufReader::new(output.take(1024 * 1024)).lines() {
45
+ let Ok(line) = line else { break };
46
+ if line.len() > 262_144 {
47
+ break;
48
+ }
49
+ let Ok(value) = serde_json::from_str(&line) else {
50
+ break;
51
+ };
52
+ if send.send(value).is_err() {
53
+ break;
54
+ }
55
+ }
56
+ });
57
+ let owner = Self {
58
+ child,
59
+ messages,
60
+ reader: Some(reader),
61
+ };
62
+ assert_eq!(
63
+ owner.messages.recv_timeout(Duration::from_secs(5)).unwrap()["event"],
64
+ "ready"
65
+ );
66
+ owner
67
+ }
68
+ fn response(&mut self, method: &str, params: Value) -> Value {
69
+ writeln!(
70
+ self.child.stdin.as_mut().unwrap(),
71
+ "{}",
72
+ json!({"id":method,"method":method,"params":params})
73
+ )
74
+ .unwrap();
75
+ let response = self.messages.recv_timeout(Duration::from_secs(5)).unwrap();
76
+ assert_eq!(response["id"], method);
77
+ response
78
+ }
79
+ fn call(&mut self, method: &str, params: Value) -> Value {
80
+ let response = self.response(method, params);
81
+ assert_eq!(response["success"], true, "{response}");
82
+ response["result"].clone()
83
+ }
84
+ }
85
+ impl Drop for NativeOwner {
86
+ fn drop(&mut self) {
87
+ let _ = self.child.kill();
88
+ let _ = self.child.wait();
89
+ if let Some(reader) = self.reader.take() {
90
+ let _ = reader.join();
91
+ }
92
+ }
93
+ }
94
+
95
+ #[test]
96
+ #[ignore = "requires the explicitly isolated QEMU qualification guest"]
97
+ fn inherited_namespace_binds_policy_and_persistent_recovery() {
98
+ isolated();
99
+ ip(&["netns", "add", "fdtarget"]);
100
+ let namespace = File::open("/run/netns/fdtarget").unwrap();
101
+ let metadata = namespace.metadata().unwrap();
102
+ let initial = fs::metadata("/proc/thread-self/ns/net").unwrap();
103
+ assert_ne!(metadata.ino(), initial.ino());
104
+ let mut child = NativeOwner::start(&namespace);
105
+ let pid = child.child.id();
106
+ let status = child.call("openOwner", serde_json::to_value(options("nsfd")).unwrap());
107
+ assert_eq!(
108
+ status["identity"]["namespaceDevice"],
109
+ metadata.dev().to_string()
110
+ );
111
+ assert_eq!(
112
+ status["identity"]["namespaceInode"],
113
+ metadata.ino().to_string()
114
+ );
115
+ assert_eq!(
116
+ fs::read_dir(format!("/proc/{pid}/task")).unwrap().count(),
117
+ 1
118
+ );
119
+ let target_link = fs::read_link(format!("/proc/{pid}/ns/net")).unwrap();
120
+ for fd in fs::read_dir(format!("/proc/{pid}/fd")).unwrap() {
121
+ if let Ok(target) = fs::read_link(fd.unwrap().path()) {
122
+ assert_ne!(
123
+ target, target_link,
124
+ "child namespace descriptor must be closed"
125
+ );
126
+ }
127
+ }
128
+ let first = child.call(
129
+ "reconcilePolicy",
130
+ serde_json::to_value(Transition {
131
+ previous: None,
132
+ target: empty(1),
133
+ })
134
+ .unwrap(),
135
+ );
136
+ assert!(Owner::new(options("nsfd"))
137
+ .unwrap()
138
+ .graph()
139
+ .unwrap()
140
+ .is_none());
141
+ assert!(in_namespace("fdtarget", || Owner::new(options("nsfd"))
142
+ .unwrap()
143
+ .graph()
144
+ .unwrap()
145
+ .is_some()));
146
+ assert_eq!(
147
+ unsafe { libc::ioctl(namespace.as_raw_fd(), libc::NS_GET_NSTYPE) },
148
+ libc::CLONE_NEWNET
149
+ );
150
+ drop(child); // PERSIST must retain the target graph after native process loss.
151
+ assert!(!std::path::Path::new(&format!("/proc/{pid}")).exists());
152
+ assert!(in_namespace("fdtarget", || Owner::new(options("nsfd"))
153
+ .unwrap()
154
+ .graph()
155
+ .unwrap()
156
+ .is_some()));
157
+
158
+ let previous: Applied = serde_json::from_value(first).unwrap();
159
+ let transition = serde_json::to_value(Transition {
160
+ previous: Some(previous),
161
+ target: empty(2),
162
+ })
163
+ .unwrap();
164
+ let wrong_namespace = File::open("/proc/thread-self/ns/net").unwrap();
165
+ let mut wrong = NativeOwner::start(&wrong_namespace);
166
+ wrong.call("openOwner", serde_json::to_value(options("nsfd")).unwrap());
167
+ let denied = wrong.response("reconcilePolicy", transition.clone());
168
+ assert_eq!(denied["success"], false);
169
+ assert_eq!(denied["errorCode"], "CONFLICT");
170
+ assert!(Owner::new(options("nsfd"))
171
+ .unwrap()
172
+ .graph()
173
+ .unwrap()
174
+ .is_none());
175
+ drop(wrong);
176
+ assert!(in_namespace("fdtarget", || Owner::new(options("nsfd"))
177
+ .unwrap()
178
+ .graph()
179
+ .unwrap()
180
+ .is_some()));
181
+
182
+ let mut recovered = NativeOwner::start(&namespace);
183
+ recovered.call("openOwner", serde_json::to_value(options("nsfd")).unwrap());
184
+ drop(namespace); // The entered child keeps membership; caller capability is not consumed by it.
185
+ let applied = recovered.call("reconcilePolicy", transition);
186
+ assert_eq!(applied["receipt"]["revision"], 2);
187
+ assert_eq!(
188
+ applied["receipt"]["identity"]["namespaceInode"],
189
+ metadata.ino().to_string()
190
+ );
191
+ assert_eq!(recovered.call("inspectPolicy", json!({}))["enforced"], true);
192
+ assert_eq!(recovered.call("closeOwner", json!({}))["released"], true);
193
+ drop(recovered);
194
+ assert!(in_namespace("fdtarget", || Owner::new(options("nsfd"))
195
+ .unwrap()
196
+ .graph()
197
+ .unwrap()
198
+ .is_none()));
199
+ assert!(Owner::new(options("nsfd"))
200
+ .unwrap()
201
+ .graph()
202
+ .unwrap()
203
+ .is_none());
204
+ assert_eq!(
205
+ fs::metadata("/proc/thread-self/ns/net").unwrap().ino(),
206
+ initial.ino()
207
+ );
208
+ ip(&["netns", "delete", "fdtarget"]);
209
+ }
210
+
211
+ #[test]
212
+ #[ignore = "requires the explicitly isolated QEMU qualification guest"]
213
+ fn valid_descriptor_without_entry_permission_never_becomes_ready() {
214
+ isolated();
215
+ let namespace = File::open("/proc/thread-self/ns/net").unwrap();
216
+ let mut child = command(&namespace)
217
+ .uid(65534)
218
+ .gid(65534)
219
+ .stdin(Stdio::null())
220
+ .stderr(Stdio::piped())
221
+ .spawn()
222
+ .unwrap();
223
+ let deadline = Instant::now() + Duration::from_secs(5);
224
+ while child.try_wait().unwrap().is_none() {
225
+ if Instant::now() >= deadline {
226
+ let _ = child.kill();
227
+ let _ = child.wait();
228
+ panic!("denied namespace entry did not exit");
229
+ }
230
+ thread::sleep(Duration::from_millis(10));
231
+ }
232
+ let output = child.wait_with_output().unwrap();
233
+ assert_eq!(output.status.code(), Some(1));
234
+ assert!(output.stdout.is_empty());
235
+ assert!(String::from_utf8(output.stderr)
236
+ .unwrap()
237
+ .contains("network namespace entry failed"));
238
+ assert_eq!(
239
+ unsafe { libc::ioctl(namespace.as_raw_fd(), libc::NS_GET_NSTYPE) },
240
+ libc::CLONE_NEWNET
241
+ );
242
+ }
@@ -0,0 +1,200 @@
1
+ //! Guest-only Ethernet observation/injection for packet-path qualifications.
2
+ use super::*;
3
+ use std::os::fd::{AsRawFd, FromRawFd, OwnedFd};
4
+ use std::time::{Duration, Instant};
5
+
6
+ pub(super) struct PacketSocket {
7
+ fd: OwnedFd,
8
+ index: i32,
9
+ }
10
+ impl PacketSocket {
11
+ pub(super) fn open(name: &str) -> Self {
12
+ guest_guard();
13
+ let name = std::ffi::CString::new(name).unwrap();
14
+ let index = unsafe { libc::if_nametoindex(name.as_ptr()) } as i32;
15
+ assert!(index > 0);
16
+ let raw = unsafe {
17
+ libc::socket(
18
+ libc::AF_PACKET,
19
+ libc::SOCK_DGRAM | libc::SOCK_CLOEXEC,
20
+ i32::from((libc::ETH_P_IP as u16).to_be()),
21
+ )
22
+ };
23
+ assert!(
24
+ raw >= 0,
25
+ "packet socket: {}",
26
+ std::io::Error::last_os_error()
27
+ );
28
+ let fd = unsafe { OwnedFd::from_raw_fd(raw) };
29
+ let mut address: libc::sockaddr_ll = unsafe { std::mem::zeroed() };
30
+ address.sll_family = libc::AF_PACKET as u16;
31
+ address.sll_protocol = (libc::ETH_P_IP as u16).to_be();
32
+ address.sll_ifindex = index;
33
+ assert_eq!(
34
+ unsafe {
35
+ libc::bind(
36
+ raw,
37
+ (&address as *const libc::sockaddr_ll).cast(),
38
+ std::mem::size_of_val(&address) as _,
39
+ )
40
+ },
41
+ 0
42
+ );
43
+ Self { fd, index }
44
+ }
45
+ pub(super) fn send(&self, mac: [u8; 6], packet: &[u8]) {
46
+ let mut address: libc::sockaddr_ll = unsafe { std::mem::zeroed() };
47
+ address.sll_family = libc::AF_PACKET as u16;
48
+ address.sll_protocol = (libc::ETH_P_IP as u16).to_be();
49
+ address.sll_ifindex = self.index;
50
+ address.sll_halen = 6;
51
+ address.sll_addr[..6].copy_from_slice(&mac);
52
+ assert_eq!(
53
+ unsafe {
54
+ libc::sendto(
55
+ self.fd.as_raw_fd(),
56
+ packet.as_ptr().cast(),
57
+ packet.len(),
58
+ 0,
59
+ (&address as *const libc::sockaddr_ll).cast(),
60
+ std::mem::size_of_val(&address) as _,
61
+ )
62
+ },
63
+ packet.len() as isize
64
+ );
65
+ }
66
+ pub(super) fn matching(&self, mut matches: impl FnMut(&[u8]) -> bool) -> Option<Vec<u8>> {
67
+ let deadline = Instant::now() + Duration::from_millis(500);
68
+ loop {
69
+ let remaining = deadline
70
+ .saturating_duration_since(Instant::now())
71
+ .as_millis();
72
+ if remaining == 0 {
73
+ return None;
74
+ }
75
+ let mut poll = libc::pollfd {
76
+ fd: self.fd.as_raw_fd(),
77
+ events: libc::POLLIN,
78
+ revents: 0,
79
+ };
80
+ let ready = unsafe { libc::poll(&mut poll, 1, remaining as i32) };
81
+ assert!(ready >= 0);
82
+ if ready == 0 {
83
+ return None;
84
+ }
85
+ let mut packet = vec![0; 65536];
86
+ let size = unsafe {
87
+ libc::recv(
88
+ self.fd.as_raw_fd(),
89
+ packet.as_mut_ptr().cast(),
90
+ packet.len(),
91
+ libc::MSG_TRUNC,
92
+ )
93
+ };
94
+ assert!(size >= 0 && (size as usize) <= packet.len());
95
+ packet.truncate(size as usize);
96
+ if matches(&packet) {
97
+ return Some(packet);
98
+ }
99
+ }
100
+ }
101
+ }
102
+ pub(super) fn mac(name: &str) -> [u8; 6] {
103
+ let output = std::process::Command::new("/bin/ip")
104
+ .args(["-j", "link", "show", "dev", name])
105
+ .output()
106
+ .unwrap();
107
+ assert!(output.status.success());
108
+ let value = serde_json::from_slice::<serde_json::Value>(&output.stdout).unwrap();
109
+ value[0]["address"]
110
+ .as_str()
111
+ .unwrap()
112
+ .split(':')
113
+ .map(|byte| u8::from_str_radix(byte, 16).unwrap())
114
+ .collect::<Vec<_>>()
115
+ .try_into()
116
+ .unwrap()
117
+ }
118
+ fn checksum(bytes: &[u8]) -> u16 {
119
+ let mut sum: u32 = bytes
120
+ .chunks(2)
121
+ .map(|bytes| u32::from(bytes[0]) * 256 + u32::from(*bytes.get(1).unwrap_or(&0)))
122
+ .sum();
123
+ while sum >> 16 != 0 {
124
+ sum = (sum & 0xffff) + (sum >> 16);
125
+ }
126
+ !(sum as u16)
127
+ }
128
+ pub(super) fn tcp(
129
+ source: [u8; 4],
130
+ destination: [u8; 4],
131
+ source_port: u16,
132
+ destination_port: u16,
133
+ flags: u8,
134
+ ) -> Vec<u8> {
135
+ let mut packet = vec![0; 40];
136
+ packet[0] = 0x45;
137
+ packet[2..4].copy_from_slice(&40_u16.to_be_bytes());
138
+ packet[8] = 64;
139
+ packet[9] = 6;
140
+ packet[12..16].copy_from_slice(&source);
141
+ packet[16..20].copy_from_slice(&destination);
142
+ let sum = checksum(&packet[..20]);
143
+ packet[10..12].copy_from_slice(&sum.to_be_bytes());
144
+ packet[20..22].copy_from_slice(&source_port.to_be_bytes());
145
+ packet[22..24].copy_from_slice(&destination_port.to_be_bytes());
146
+ packet[24..28].copy_from_slice(&100_u32.to_be_bytes());
147
+ packet[32] = 0x50;
148
+ packet[33] = flags;
149
+ packet[34..36].copy_from_slice(&65535_u16.to_be_bytes());
150
+ let mut pseudo = Vec::new();
151
+ pseudo.extend(source);
152
+ pseudo.extend(destination);
153
+ pseudo.extend([0, 6, 0, 20]);
154
+ pseudo.extend(&packet[20..]);
155
+ let sum = checksum(&pseudo);
156
+ packet[36..38].copy_from_slice(&sum.to_be_bytes());
157
+ packet
158
+ }
159
+
160
+ pub(super) fn udp(
161
+ source: [u8; 4],
162
+ destination: [u8; 4],
163
+ source_port: u16,
164
+ destination_port: u16,
165
+ ) -> Vec<u8> {
166
+ let mut packet = vec![0; 32];
167
+ packet[0] = 0x45;
168
+ packet[2..4].copy_from_slice(&32_u16.to_be_bytes());
169
+ packet[8] = 64;
170
+ packet[9] = 17;
171
+ packet[12..16].copy_from_slice(&source);
172
+ packet[16..20].copy_from_slice(&destination);
173
+ let sum = checksum(&packet[..20]);
174
+ packet[10..12].copy_from_slice(&sum.to_be_bytes());
175
+ packet[20..22].copy_from_slice(&source_port.to_be_bytes());
176
+ packet[22..24].copy_from_slice(&destination_port.to_be_bytes());
177
+ packet[24..26].copy_from_slice(&12_u16.to_be_bytes());
178
+ packet[28..].copy_from_slice(b"frag");
179
+ packet
180
+ }
181
+ pub(super) fn fragments(packet: &[u8], id: u16) -> [Vec<u8>; 2] {
182
+ assert_eq!(packet.len(), 32);
183
+ assert_eq!(packet[0], 0x45);
184
+ let fragment = |body: &[u8], offset: u16| {
185
+ let mut result = packet[..20].to_vec();
186
+ result.extend(body);
187
+ let length = result.len() as u16;
188
+ result[2..4].copy_from_slice(&length.to_be_bytes());
189
+ result[4..6].copy_from_slice(&id.to_be_bytes());
190
+ result[6..8].copy_from_slice(&offset.to_be_bytes());
191
+ result[10..12].fill(0);
192
+ let sum = checksum(&result[..20]);
193
+ result[10..12].copy_from_slice(&sum.to_be_bytes());
194
+ result
195
+ };
196
+ [
197
+ fragment(&packet[20..28], 0x2000),
198
+ fragment(&packet[28..], 1),
199
+ ]
200
+ }
@@ -1,6 +1,30 @@
1
1
  use super::*;
2
2
 
3
- fn isolated() {
3
+ #[path = "owner_namespace_tests.rs"]
4
+ mod namespace_tests;
5
+
6
+ #[path = "owner_egress_identity_tests.rs"]
7
+ mod egress_identity_tests;
8
+
9
+ #[path = "owner_link_tests.rs"]
10
+ mod link_tests;
11
+
12
+ #[path = "owner_egress_tests.rs"]
13
+ mod egress_tests;
14
+
15
+ #[path = "owner_egress_traffic_tests.rs"]
16
+ mod egress_traffic_tests;
17
+
18
+ #[path = "owner_packet_fixture.rs"]
19
+ mod packet_fixture;
20
+
21
+ #[path = "owner_host_traffic_tests.rs"]
22
+ mod host_traffic_tests;
23
+
24
+ #[path = "owner_coexistence_tests.rs"]
25
+ mod coexistence_tests;
26
+
27
+ fn guest_guard() {
4
28
  assert_eq!(
5
29
  std::env::var("SMARTNFTABLES_ISOLATED_NATIVE").as_deref(),
6
30
  Ok("qemu-6.18.35")
@@ -9,6 +33,10 @@ fn isolated() {
9
33
  .unwrap()
10
34
  .contains("smartnftables_qualification=1"));
11
35
  assert_eq!(unsafe { libc::geteuid() }, 0);
36
+ }
37
+
38
+ fn isolated() {
39
+ guest_guard();
12
40
  let initial = context().unwrap();
13
41
  assert_eq!(
14
42
  unsafe { libc::unshare(libc::CLONE_NEWNET | libc::CLONE_NEWNS) },
@@ -276,7 +304,7 @@ fn native_interface_ipv4_directed_grants_and_spoof_denial() {
276
304
  assert_eq!(
277
305
  owner.reconcile(Transition {
278
306
  previous: None,
279
- target: invalid_pair.prepare().unwrap()
307
+ target: invalid_pair.prepare().unwrap().into()
280
308
  }),
281
309
  Err(Error::Conflict)
282
310
  );
@@ -286,7 +314,7 @@ fn native_interface_ipv4_directed_grants_and_spoof_denial() {
286
314
  assert_eq!(
287
315
  owner.reconcile(Transition {
288
316
  previous: None,
289
- target: policy.clone().prepare().unwrap()
317
+ target: policy.clone().prepare().unwrap().into()
290
318
  }),
291
319
  Err(Error::Conflict)
292
320
  );
@@ -295,7 +323,7 @@ fn native_interface_ipv4_directed_grants_and_spoof_denial() {
295
323
  let current = owner
296
324
  .reconcile(Transition {
297
325
  previous: None,
298
- target: policy.clone().prepare().unwrap(),
326
+ target: policy.clone().prepare().unwrap().into(),
299
327
  })
300
328
  .expect("apply interface policy");
301
329
  assert!(owner.inspect().enforced);
@@ -333,7 +361,7 @@ fn native_interface_ipv4_directed_grants_and_spoof_denial() {
333
361
  let revoked = owner
334
362
  .reconcile(Transition {
335
363
  previous: Some(current),
336
- target: denied.prepare().unwrap(),
364
+ target: denied.prepare().unwrap().into(),
337
365
  })
338
366
  .expect("atomic complete deny");
339
367
  udp(&client, &server, false);
@@ -361,6 +389,7 @@ fn empty(revision: u64) -> Prepared {
361
389
  }
362
390
  .prepare()
363
391
  .unwrap()
392
+ .into()
364
393
  }
365
394
 
366
395
  #[test]
@@ -696,14 +725,14 @@ fn native_tun_forwarding_binds_remote_ingress_and_rejects_tap() {
696
725
  assert_eq!(
697
726
  owner.reconcile(Transition {
698
727
  previous: None,
699
- target: invalid.prepare().unwrap()
728
+ target: invalid.prepare().unwrap().into()
700
729
  }),
701
730
  Err(Error::Conflict)
702
731
  );
703
732
  let applied = owner
704
733
  .reconcile(Transition {
705
734
  previous: None,
706
- target: policy.prepare().unwrap(),
735
+ target: policy.prepare().unwrap().into(),
707
736
  })
708
737
  .unwrap();
709
738
  send_tunnel(&mut device, true);