1 // Copyright 2022 The ChromiumOS Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 //! Testing vsock.
6
7 #![cfg(any(target_os = "android", target_os = "linux"))]
8
9 use std::io::Write;
10 use std::path::Path;
11 use std::process::Command;
12 use std::process::Stdio;
13 use std::time::Duration;
14
15 use fixture::utils::retry;
16 use fixture::utils::retry_with_delay;
17 use fixture::utils::ChildExt;
18 use fixture::utils::CommandExt;
19 use fixture::vhost_user::CmdType;
20 use fixture::vhost_user::Config as VuConfig;
21 use fixture::vhost_user::VhostUserBackend;
22 use fixture::vm::Config;
23 use fixture::vm::TestVm;
24 use rand::Rng;
25 use tempfile::tempdir;
26 use tempfile::NamedTempFile;
27
28 const ANY_CID: &str = "4294967295"; // -1U
29 const HOST_CID: u64 = 2;
30
31 const SERVER_TIMEOUT: Duration = Duration::from_secs(3);
32 const NCAT_RETRIES: usize = 15;
33 const NCAT_RETRY_DELAY: Duration = Duration::from_millis(300);
34
35 const MESSAGE_TO_HOST: &str = "Connection from the host is successfully established";
36 const MESSAGE_TO_GUEST: &str = "Connection from the guest is successfully established";
37
38 // generate a random CID to avoid conflicts with other VMs run on different processes
generate_guest_cid() -> u3239 fn generate_guest_cid() -> u32 {
40 // avoid special CIDs and negative values
41 rand::thread_rng().gen_range(3..0x8000_0000)
42 }
43
generate_vhost_port() -> u3244 fn generate_vhost_port() -> u32 {
45 rand::thread_rng().gen_range(10000..99999)
46 }
47
48 #[test]
host_to_guest()49 fn host_to_guest() {
50 let guest_port = generate_vhost_port();
51 let guest_cid = generate_guest_cid();
52 let config = Config::new().extra_args(vec!["--cid".to_string(), guest_cid.to_string()]);
53 let mut vm = TestVm::new(config).unwrap();
54 host_to_guest_connection(&mut vm, guest_cid, guest_port);
55 }
56
57 #[test]
host_to_guest_disable_sandbox()58 fn host_to_guest_disable_sandbox() {
59 let guest_port = generate_vhost_port();
60 let guest_cid = generate_guest_cid();
61 let config = Config::new()
62 .extra_args(vec!["--cid".to_string(), guest_cid.to_string()])
63 .disable_sandbox();
64 let mut vm = TestVm::new(config).unwrap();
65 host_to_guest_connection(&mut vm, guest_cid, guest_port);
66 }
67
68 #[test]
host_to_guest_snapshot_restore()69 fn host_to_guest_snapshot_restore() {
70 let guest_port = generate_vhost_port();
71 let guest_cid = generate_guest_cid();
72 let config = Config::new().extra_args(vec![
73 "--cid".to_string(),
74 guest_cid.to_string(),
75 "--no-usb".to_string(),
76 ]);
77 let mut vm = TestVm::new(config).unwrap();
78 host_to_guest_connection(&mut vm, guest_cid, guest_port);
79 let dir = tempdir().unwrap();
80 let snap = dir.path().join("snapshot.bkp");
81 vm.snapshot(&snap).unwrap();
82 let config = Config::new().extra_args(vec![
83 "--cid".to_string(),
84 guest_cid.to_string(),
85 "--restore".to_string(),
86 snap.to_str().unwrap().to_string(),
87 "--no-usb".to_string(),
88 ]);
89 drop(vm);
90 vm = TestVm::new_restore(config).unwrap();
91 host_to_guest_connection(&mut vm, guest_cid, guest_port);
92 }
93
94 #[test]
host_to_guest_disable_sandbox_snapshot_restore()95 fn host_to_guest_disable_sandbox_snapshot_restore() {
96 let guest_port = generate_vhost_port();
97 let guest_cid = generate_guest_cid();
98 let config = Config::new().extra_args(vec![
99 "--cid".to_string(),
100 guest_cid.to_string(),
101 "--no-usb".to_string(),
102 ]);
103 let mut vm = TestVm::new(config.disable_sandbox()).unwrap();
104 host_to_guest_connection(&mut vm, guest_cid, guest_port);
105 let dir = tempdir().unwrap();
106 let snap = dir.path().join("snapshot.bkp");
107 vm.snapshot(&snap).unwrap();
108 let config = Config::new().extra_args(vec![
109 "--cid".to_string(),
110 guest_cid.to_string(),
111 "--restore".to_string(),
112 snap.to_str().unwrap().to_string(),
113 "--no-usb".to_string(),
114 ]);
115 drop(vm);
116 vm = TestVm::new_restore(config.disable_sandbox()).unwrap();
117 host_to_guest_connection(&mut vm, guest_cid, guest_port);
118 }
119
host_to_guest_connection(vm: &mut TestVm, guest_cid: u32, guest_port: u32)120 fn host_to_guest_connection(vm: &mut TestVm, guest_cid: u32, guest_port: u32) {
121 let guest_cmd = vm
122 .exec_in_guest_async(&format!(
123 "echo {MESSAGE_TO_HOST} | ncat -v -l --vsock --send-only {ANY_CID} {guest_port}"
124 ))
125 .unwrap();
126
127 let output = retry_with_delay(
128 || {
129 // This will instantly fail if the guest isn't listening on the port yet, so we need to
130 // retry with a delay.
131 Command::new("ncat")
132 .args([
133 "-v",
134 "--recv-only",
135 "--vsock",
136 &guest_cid.to_string(),
137 &guest_port.to_string(),
138 ])
139 .stderr(Stdio::inherit())
140 .log()
141 .output_checked()
142 },
143 NCAT_RETRIES,
144 NCAT_RETRY_DELAY,
145 )
146 .unwrap();
147
148 let host_stdout = std::str::from_utf8(&output.stdout).unwrap();
149 assert_eq!(host_stdout.trim(), MESSAGE_TO_HOST);
150
151 guest_cmd.wait_ok(vm).unwrap();
152 }
153
154 #[test]
guest_to_host()155 fn guest_to_host() {
156 let host_port = generate_vhost_port();
157 let guest_cid = generate_guest_cid();
158 let config = Config::new().extra_args(vec!["--cid".to_string(), guest_cid.to_string()]);
159 let mut vm = TestVm::new(config).unwrap();
160 guest_to_host_connection(&mut vm, host_port);
161 }
162
163 #[test]
guest_to_host_disable_sandbox()164 fn guest_to_host_disable_sandbox() {
165 let host_port = generate_vhost_port();
166 let guest_cid = generate_guest_cid();
167 let config = Config::new()
168 .extra_args(vec!["--cid".to_string(), guest_cid.to_string()])
169 .disable_sandbox();
170 let mut vm = TestVm::new(config).unwrap();
171 guest_to_host_connection(&mut vm, host_port);
172 }
173
174 #[test]
guest_to_host_snapshot_restore()175 fn guest_to_host_snapshot_restore() {
176 let host_port = generate_vhost_port();
177 let guest_cid = generate_guest_cid();
178 let config = Config::new().extra_args(vec![
179 "--cid".to_string(),
180 guest_cid.to_string(),
181 "--no-usb".to_string(),
182 ]);
183 let mut vm = TestVm::new(config).unwrap();
184 guest_to_host_connection(&mut vm, host_port);
185 let dir = tempdir().unwrap();
186 let snap = dir.path().join("snapshot.bkp");
187 vm.snapshot(&snap).unwrap();
188 let config = Config::new().extra_args(vec![
189 "--cid".to_string(),
190 guest_cid.to_string(),
191 "--no-usb".to_string(),
192 "--restore".to_string(),
193 snap.to_str().unwrap().to_string(),
194 ]);
195 drop(vm);
196 vm = TestVm::new_restore(config).unwrap();
197 guest_to_host_connection(&mut vm, host_port);
198 }
199
200 #[test]
guest_to_host_disable_sandbox_snapshot_restore()201 fn guest_to_host_disable_sandbox_snapshot_restore() {
202 let host_port = generate_vhost_port();
203 let guest_cid = generate_guest_cid();
204 let config = Config::new()
205 .extra_args(vec![
206 "--cid".to_string(),
207 guest_cid.to_string(),
208 "--no-usb".to_string(),
209 ])
210 .disable_sandbox();
211 let mut vm = TestVm::new(config).unwrap();
212 guest_to_host_connection(&mut vm, host_port);
213 let dir = tempdir().unwrap();
214 let snap = dir.path().join("snapshot.bkp");
215 vm.snapshot(&snap).unwrap();
216 let config = Config::new().extra_args(vec![
217 "--cid".to_string(),
218 guest_cid.to_string(),
219 "--no-usb".to_string(),
220 "--restore".to_string(),
221 snap.to_str().unwrap().to_string(),
222 ]);
223 drop(vm);
224 vm = TestVm::new_restore(config.disable_sandbox()).unwrap();
225 guest_to_host_connection(&mut vm, host_port);
226 }
227
guest_to_host_connection(vm: &mut TestVm, host_port: u32)228 fn guest_to_host_connection(vm: &mut TestVm, host_port: u32) {
229 let mut host_ncat = Command::new("ncat")
230 .arg("-l")
231 .arg("--send-only")
232 .args(["--vsock", ANY_CID, &host_port.to_string()])
233 .stdin(Stdio::piped())
234 .log()
235 .spawn()
236 .expect("failed to execute process");
237
238 host_ncat
239 .stdin
240 .take()
241 .unwrap()
242 .write_all(MESSAGE_TO_GUEST.as_bytes())
243 .unwrap();
244
245 let cmd = format!("ncat --recv-only --vsock {HOST_CID} {host_port}; echo ''");
246 let guest_stdout = retry(|| vm.exec_in_guest(&cmd), NCAT_RETRIES).unwrap();
247 assert_eq!(guest_stdout.stdout.trim(), MESSAGE_TO_GUEST);
248
249 host_ncat.wait_with_timeout(SERVER_TIMEOUT).unwrap();
250 }
251
create_vu_config(cmd_type: CmdType, socket: &Path, cid: u32) -> VuConfig252 fn create_vu_config(cmd_type: CmdType, socket: &Path, cid: u32) -> VuConfig {
253 let socket_path = socket.to_str().unwrap();
254 println!("cid={cid}, socket={socket_path}");
255 match cmd_type {
256 CmdType::Device => VuConfig::new(cmd_type, "vsock").extra_args(vec![
257 "vsock".to_string(),
258 "--socket-path".to_string(),
259 socket_path.to_string(),
260 "--cid".to_string(),
261 cid.to_string(),
262 ]),
263 CmdType::Devices => VuConfig::new(cmd_type, "vsock").extra_args(vec![
264 "--vsock".to_string(),
265 format!("vhost={},cid={}", socket_path, cid),
266 ]),
267 }
268 }
269
270 #[test]
vhost_user_host_to_guest()271 fn vhost_user_host_to_guest() {
272 let guest_port = generate_vhost_port();
273 let guest_cid = generate_guest_cid();
274 let socket = NamedTempFile::new().unwrap();
275
276 let vu_config = create_vu_config(CmdType::Device, socket.path(), guest_cid);
277 let _vu_device = VhostUserBackend::new(vu_config).unwrap();
278
279 let config = Config::new().extra_args(vec![
280 "--vhost-user".to_string(),
281 format!("vsock,socket={}", socket.path().to_str().unwrap()),
282 ]);
283
284 let mut vm = TestVm::new(config).unwrap();
285 host_to_guest_connection(&mut vm, guest_cid, guest_port);
286 }
287
288 #[test]
vhost_user_host_to_guest_with_devices()289 fn vhost_user_host_to_guest_with_devices() {
290 let guest_port = generate_vhost_port();
291 let guest_cid = generate_guest_cid();
292 let socket = NamedTempFile::new().unwrap();
293
294 let vu_config = create_vu_config(CmdType::Devices, socket.path(), guest_cid);
295 let _vu_device = VhostUserBackend::new(vu_config).unwrap();
296
297 let config = Config::new().extra_args(vec![
298 "--vhost-user".to_string(),
299 format!("vsock,socket={}", socket.path().to_str().unwrap()),
300 ]);
301
302 let mut vm = TestVm::new(config).unwrap();
303 host_to_guest_connection(&mut vm, guest_cid, guest_port);
304 }
305
306 #[test]
vhost_user_guest_to_host()307 fn vhost_user_guest_to_host() {
308 let host_port = generate_vhost_port();
309 let guest_cid = generate_guest_cid();
310 let socket = NamedTempFile::new().unwrap();
311
312 let vu_config = create_vu_config(CmdType::Device, socket.path(), guest_cid);
313 let _vu_device = VhostUserBackend::new(vu_config).unwrap();
314
315 let config = Config::new().extra_args(vec![
316 "--vhost-user".to_string(),
317 format!("vsock,socket={}", socket.path().to_str().unwrap()),
318 ]);
319
320 let mut vm = TestVm::new(config).unwrap();
321 guest_to_host_connection(&mut vm, host_port);
322 }
323
324 #[test]
vhost_user_guest_to_host_with_devices()325 fn vhost_user_guest_to_host_with_devices() {
326 let host_port = generate_vhost_port();
327 let guest_cid = generate_guest_cid();
328 let socket = NamedTempFile::new().unwrap();
329
330 let vu_config = create_vu_config(CmdType::Devices, socket.path(), guest_cid);
331 let _vu_device = VhostUserBackend::new(vu_config).unwrap();
332
333 let config = Config::new().extra_args(vec![
334 "--vhost-user".to_string(),
335 format!("vsock,socket={}", socket.path().to_str().unwrap()),
336 ]);
337
338 let mut vm = TestVm::new(config).unwrap();
339 guest_to_host_connection(&mut vm, host_port);
340 }
341