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]
host_to_guest()47 fn host_to_guest() {
48 let guest_port = generate_vhost_port();
49 let guest_cid = generate_guest_cid();
50 let config = Config::new().extra_args(vec!["--cid".to_string(), guest_cid.to_string()]);
51 let mut vm = TestVm::new(config).unwrap();
52 host_to_guest_connection(&mut vm, guest_cid, guest_port);
53 }
54
55 #[test]
host_to_guest_disable_sandbox()56 fn host_to_guest_disable_sandbox() {
57 let guest_port = generate_vhost_port();
58 let guest_cid = generate_guest_cid();
59 let config = Config::new()
60 .extra_args(vec!["--cid".to_string(), guest_cid.to_string()])
61 .disable_sandbox();
62 let mut vm = TestVm::new(config).unwrap();
63 host_to_guest_connection(&mut vm, guest_cid, guest_port);
64 }
65
66 #[test]
host_to_guest_snapshot_restore()67 fn host_to_guest_snapshot_restore() {
68 let guest_port = generate_vhost_port();
69 let guest_cid = generate_guest_cid();
70 let config = Config::new()
71 .extra_args(vec![
72 "--cid".to_string(),
73 guest_cid.to_string(),
74 "--no-usb".to_string(),
75 ])
76 .with_stdout_hardware("legacy-virtio-console");
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()
83 .extra_args(vec![
84 "--cid".to_string(),
85 guest_cid.to_string(),
86 "--restore".to_string(),
87 snap.to_str().unwrap().to_string(),
88 "--no-usb".to_string(),
89 ])
90 .with_stdout_hardware("legacy-virtio-console");
91 drop(vm);
92 vm = TestVm::new_restore(config).unwrap();
93 host_to_guest_connection(&mut vm, guest_cid, guest_port);
94 }
95
96 #[test]
host_to_guest_disable_sandbox_snapshot_restore()97 fn host_to_guest_disable_sandbox_snapshot_restore() {
98 let guest_port = generate_vhost_port();
99 let guest_cid = generate_guest_cid();
100 let config = Config::new()
101 .extra_args(vec![
102 "--cid".to_string(),
103 guest_cid.to_string(),
104 "--no-usb".to_string(),
105 ])
106 .with_stdout_hardware("legacy-virtio-console");
107 let mut vm = TestVm::new(config.disable_sandbox()).unwrap();
108 host_to_guest_connection(&mut vm, guest_cid, guest_port);
109 let dir = tempdir().unwrap();
110 let snap = dir.path().join("snapshot.bkp");
111 vm.snapshot(&snap).unwrap();
112 let config = Config::new()
113 .extra_args(vec![
114 "--cid".to_string(),
115 guest_cid.to_string(),
116 "--restore".to_string(),
117 snap.to_str().unwrap().to_string(),
118 "--no-usb".to_string(),
119 ])
120 .with_stdout_hardware("legacy-virtio-console");
121 drop(vm);
122 vm = TestVm::new_restore(config.disable_sandbox()).unwrap();
123 host_to_guest_connection(&mut vm, guest_cid, guest_port);
124 }
125
host_to_guest_connection(vm: &mut TestVm, guest_cid: u32, guest_port: u32)126 fn host_to_guest_connection(vm: &mut TestVm, guest_cid: u32, guest_port: u32) {
127 let guest_cmd = vm
128 .exec_in_guest_async(&format!(
129 "echo {MESSAGE_TO_HOST} | ncat -l --vsock --send-only {ANY_CID} {guest_port}"
130 ))
131 .unwrap();
132
133 let output = retry(
134 || {
135 Command::new("ncat")
136 .args([
137 "--recv-only",
138 "--vsock",
139 &guest_cid.to_string(),
140 &guest_port.to_string(),
141 ])
142 .stderr(Stdio::inherit())
143 .log()
144 .output_checked()
145 },
146 NCAT_RETRIES,
147 )
148 .unwrap();
149
150 let host_stdout = std::str::from_utf8(&output.stdout).unwrap();
151 assert_eq!(host_stdout.trim(), MESSAGE_TO_HOST);
152
153 guest_cmd.wait_ok(vm).unwrap();
154 }
155
156 #[test]
guest_to_host()157 fn guest_to_host() {
158 let host_port = generate_vhost_port();
159 let guest_cid = generate_guest_cid();
160 let config = Config::new().extra_args(vec!["--cid".to_string(), guest_cid.to_string()]);
161 let mut vm = TestVm::new(config).unwrap();
162 guest_to_host_connection(&mut vm, host_port);
163 }
164
165 #[test]
guest_to_host_disable_sandbox()166 fn guest_to_host_disable_sandbox() {
167 let host_port = generate_vhost_port();
168 let guest_cid = generate_guest_cid();
169 let config = Config::new()
170 .extra_args(vec!["--cid".to_string(), guest_cid.to_string()])
171 .disable_sandbox();
172 let mut vm = TestVm::new(config).unwrap();
173 guest_to_host_connection(&mut vm, host_port);
174 }
175
176 #[test]
guest_to_host_snapshot_restore()177 fn guest_to_host_snapshot_restore() {
178 let host_port = generate_vhost_port();
179 let guest_cid = generate_guest_cid();
180 let config = Config::new()
181 .extra_args(vec![
182 "--cid".to_string(),
183 guest_cid.to_string(),
184 "--no-usb".to_string(),
185 ])
186 .with_stdout_hardware("legacy-virtio-console");
187 let mut vm = TestVm::new(config).unwrap();
188 guest_to_host_connection(&mut vm, host_port);
189 let dir = tempdir().unwrap();
190 let snap = dir.path().join("snapshot.bkp");
191 vm.snapshot(&snap).unwrap();
192 let config = Config::new()
193 .extra_args(vec![
194 "--cid".to_string(),
195 guest_cid.to_string(),
196 "--no-usb".to_string(),
197 "--restore".to_string(),
198 snap.to_str().unwrap().to_string(),
199 ])
200 .with_stdout_hardware("legacy-virtio-console");
201 drop(vm);
202 vm = TestVm::new_restore(config).unwrap();
203 guest_to_host_connection(&mut vm, host_port);
204 }
205
206 #[test]
guest_to_host_disable_sandbox_snapshot_restore()207 fn guest_to_host_disable_sandbox_snapshot_restore() {
208 let host_port = generate_vhost_port();
209 let guest_cid = generate_guest_cid();
210 let config = Config::new()
211 .extra_args(vec![
212 "--cid".to_string(),
213 guest_cid.to_string(),
214 "--no-usb".to_string(),
215 ])
216 .with_stdout_hardware("legacy-virtio-console")
217 .disable_sandbox();
218 let mut vm = TestVm::new(config).unwrap();
219 guest_to_host_connection(&mut vm, host_port);
220 let dir = tempdir().unwrap();
221 let snap = dir.path().join("snapshot.bkp");
222 vm.snapshot(&snap).unwrap();
223 let config = Config::new()
224 .extra_args(vec![
225 "--cid".to_string(),
226 guest_cid.to_string(),
227 "--no-usb".to_string(),
228 "--restore".to_string(),
229 snap.to_str().unwrap().to_string(),
230 ])
231 .with_stdout_hardware("legacy-virtio-console");
232 drop(vm);
233 vm = TestVm::new_restore(config.disable_sandbox()).unwrap();
234 guest_to_host_connection(&mut vm, host_port);
235 }
236
guest_to_host_connection(vm: &mut TestVm, host_port: u32)237 fn guest_to_host_connection(vm: &mut TestVm, host_port: u32) {
238 let mut host_ncat = Command::new("ncat")
239 .arg("-l")
240 .arg("--send-only")
241 .args(["--vsock", ANY_CID, &host_port.to_string()])
242 .stdin(Stdio::piped())
243 .log()
244 .spawn()
245 .expect("failed to execute process");
246
247 host_ncat
248 .stdin
249 .take()
250 .unwrap()
251 .write_all(MESSAGE_TO_GUEST.as_bytes())
252 .unwrap();
253
254 let cmd = format!("ncat --recv-only --vsock {HOST_CID} {host_port}; echo ''");
255 let guest_stdout = retry(|| vm.exec_in_guest(&cmd), NCAT_RETRIES).unwrap();
256 assert_eq!(guest_stdout.stdout.trim(), MESSAGE_TO_GUEST);
257
258 host_ncat.wait_with_timeout(SERVER_TIMEOUT).unwrap();
259 }
260
create_vu_config(cmd_type: CmdType, socket: &Path, cid: u32) -> VuConfig261 fn create_vu_config(cmd_type: CmdType, socket: &Path, cid: u32) -> VuConfig {
262 let socket_path = socket.to_str().unwrap();
263 println!("cid={cid}, socket={socket_path}");
264 match cmd_type {
265 CmdType::Device => VuConfig::new(cmd_type, "vsock").extra_args(vec![
266 "vsock".to_string(),
267 "--socket".to_string(),
268 socket_path.to_string(),
269 "--cid".to_string(),
270 cid.to_string(),
271 ]),
272 CmdType::Devices => VuConfig::new(cmd_type, "vsock").extra_args(vec![
273 "--vsock".to_string(),
274 format!("vhost={},cid={}", socket_path, cid),
275 ]),
276 }
277 }
278
279 #[test]
280 #[ignore = "b/333090069 test is flaky"]
vhost_user_host_to_guest()281 fn vhost_user_host_to_guest() {
282 let guest_port = generate_vhost_port();
283 let guest_cid = generate_guest_cid();
284 let socket = NamedTempFile::new().unwrap();
285
286 let vu_config = create_vu_config(CmdType::Device, socket.path(), guest_cid);
287 let _vu_device = VhostUserBackend::new(vu_config).unwrap();
288
289 let config = Config::new().extra_args(vec![
290 "--vhost-user".to_string(),
291 format!("vsock,socket={}", socket.path().to_str().unwrap()),
292 ]);
293
294 let mut vm = TestVm::new(config).unwrap();
295 host_to_guest_connection(&mut vm, guest_cid, guest_port);
296 }
297
298 #[test]
299 #[ignore = "b/333090069 test is flaky"]
vhost_user_host_to_guest_with_devices()300 fn vhost_user_host_to_guest_with_devices() {
301 let guest_port = generate_vhost_port();
302 let guest_cid = generate_guest_cid();
303 let socket = NamedTempFile::new().unwrap();
304
305 let vu_config = create_vu_config(CmdType::Devices, socket.path(), guest_cid);
306 let _vu_device = VhostUserBackend::new(vu_config).unwrap();
307
308 let config = Config::new().extra_args(vec![
309 "--vhost-user".to_string(),
310 format!("vsock,socket={}", socket.path().to_str().unwrap()),
311 ]);
312
313 let mut vm = TestVm::new(config).unwrap();
314 host_to_guest_connection(&mut vm, guest_cid, guest_port);
315 }
316
317 #[test]
vhost_user_guest_to_host()318 fn vhost_user_guest_to_host() {
319 let host_port = generate_vhost_port();
320 let guest_cid = generate_guest_cid();
321 let socket = NamedTempFile::new().unwrap();
322
323 let vu_config = create_vu_config(CmdType::Device, socket.path(), guest_cid);
324 let _vu_device = VhostUserBackend::new(vu_config).unwrap();
325
326 let config = Config::new().extra_args(vec![
327 "--vhost-user".to_string(),
328 format!("vsock,socket={}", socket.path().to_str().unwrap()),
329 ]);
330
331 let mut vm = TestVm::new(config).unwrap();
332 guest_to_host_connection(&mut vm, host_port);
333 }
334
335 #[test]
vhost_user_guest_to_host_with_devices()336 fn vhost_user_guest_to_host_with_devices() {
337 let host_port = generate_vhost_port();
338 let guest_cid = generate_guest_cid();
339 let socket = NamedTempFile::new().unwrap();
340
341 let vu_config = create_vu_config(CmdType::Devices, socket.path(), guest_cid);
342 let _vu_device = VhostUserBackend::new(vu_config).unwrap();
343
344 let config = Config::new().extra_args(vec![
345 "--vhost-user".to_string(),
346 format!("vsock,socket={}", socket.path().to_str().unwrap()),
347 ]);
348
349 let mut vm = TestVm::new(config).unwrap();
350 guest_to_host_connection(&mut vm, host_port);
351 }
352