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