217 lines
6.3 KiB
Rust
217 lines
6.3 KiB
Rust
|
|
// Copyright 2022 The ChromiumOS Authors
|
||
|
|
// Use of this source code is governed by a BSD-style license that can be
|
||
|
|
// found in the LICENSE file.
|
||
|
|
|
||
|
|
//! Testing vsock.
|
||
|
|
|
||
|
|
#![cfg(unix)]
|
||
|
|
|
||
|
|
use std::io::Write;
|
||
|
|
use std::path::Path;
|
||
|
|
use std::process::Command;
|
||
|
|
use std::process::Stdio;
|
||
|
|
use std::time::Duration;
|
||
|
|
|
||
|
|
use fixture::utils::retry;
|
||
|
|
use fixture::utils::ChildExt;
|
||
|
|
use fixture::utils::CommandExt;
|
||
|
|
use fixture::vhost_user::CmdType;
|
||
|
|
use fixture::vhost_user::Config as VuConfig;
|
||
|
|
use fixture::vhost_user::VhostUserBackend;
|
||
|
|
use fixture::vm::Config;
|
||
|
|
use fixture::vm::TestVm;
|
||
|
|
use rand::Rng;
|
||
|
|
use tempfile::NamedTempFile;
|
||
|
|
|
||
|
|
const HOST_CID: u64 = 2;
|
||
|
|
|
||
|
|
const SERVER_TIMEOUT: Duration = Duration::from_secs(3);
|
||
|
|
const NCAT_RETRIES: usize = 10;
|
||
|
|
|
||
|
|
const MESSAGE_TO_HOST: &str = "Connection from the host is successfully established";
|
||
|
|
const MESSAGE_TO_GUEST: &str = "Connection from the guest is successfully established";
|
||
|
|
|
||
|
|
// generate a random CID to avoid conflicts with other VMs run on different processes
|
||
|
|
fn generate_guest_cid() -> u32 {
|
||
|
|
// avoid special CIDs and negative values
|
||
|
|
rand::thread_rng().gen_range(3..0x8000_0000)
|
||
|
|
}
|
||
|
|
|
||
|
|
fn generate_vhost_port() -> u32 {
|
||
|
|
rand::thread_rng().gen_range(10000..99999)
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn host_to_guest() {
|
||
|
|
let guest_cid = generate_guest_cid();
|
||
|
|
let config = Config::new().extra_args(vec!["--cid".to_string(), guest_cid.to_string()]);
|
||
|
|
host_to_guest_connection(config, guest_cid);
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn host_to_guest_disable_sandbox() {
|
||
|
|
let guest_cid = generate_guest_cid();
|
||
|
|
let config = Config::new()
|
||
|
|
.extra_args(vec!["--cid".to_string(), guest_cid.to_string()])
|
||
|
|
.disable_sandbox();
|
||
|
|
host_to_guest_connection(config, guest_cid);
|
||
|
|
}
|
||
|
|
|
||
|
|
fn host_to_guest_connection(config: Config, guest_cid: u32) {
|
||
|
|
let guest_port = generate_vhost_port();
|
||
|
|
let mut vm = TestVm::new(config).unwrap();
|
||
|
|
|
||
|
|
let guest_cmd = vm
|
||
|
|
.exec_in_guest_async(&format!(
|
||
|
|
"echo {MESSAGE_TO_HOST} | ncat -l --vsock --send-only {guest_port}"
|
||
|
|
))
|
||
|
|
.unwrap();
|
||
|
|
|
||
|
|
let output = retry(
|
||
|
|
|| {
|
||
|
|
Command::new("ncat")
|
||
|
|
.args([
|
||
|
|
"--recv-only",
|
||
|
|
"--vsock",
|
||
|
|
&guest_cid.to_string(),
|
||
|
|
&guest_port.to_string(),
|
||
|
|
])
|
||
|
|
.stderr(Stdio::inherit())
|
||
|
|
.log()
|
||
|
|
.output_checked()
|
||
|
|
},
|
||
|
|
NCAT_RETRIES,
|
||
|
|
)
|
||
|
|
.unwrap();
|
||
|
|
|
||
|
|
let host_stdout = std::str::from_utf8(&output.stdout).unwrap();
|
||
|
|
assert_eq!(host_stdout.trim(), MESSAGE_TO_HOST);
|
||
|
|
|
||
|
|
guest_cmd.wait(&mut vm).unwrap();
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn guest_to_host() {
|
||
|
|
let guest_cid = generate_guest_cid();
|
||
|
|
let config = Config::new().extra_args(vec!["--cid".to_string(), guest_cid.to_string()]);
|
||
|
|
guest_to_host_connection(config);
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn guest_to_host_disable_sandbox() {
|
||
|
|
let guest_cid = generate_guest_cid();
|
||
|
|
let config = Config::new()
|
||
|
|
.extra_args(vec!["--cid".to_string(), guest_cid.to_string()])
|
||
|
|
.disable_sandbox();
|
||
|
|
guest_to_host_connection(config);
|
||
|
|
}
|
||
|
|
|
||
|
|
fn guest_to_host_connection(config: Config) {
|
||
|
|
let host_port = generate_vhost_port();
|
||
|
|
let mut vm = TestVm::new(config).unwrap();
|
||
|
|
|
||
|
|
let mut host_ncat = Command::new("ncat")
|
||
|
|
.arg("-l")
|
||
|
|
.arg("--send-only")
|
||
|
|
.args(["--vsock", &host_port.to_string()])
|
||
|
|
.stdin(Stdio::piped())
|
||
|
|
.log()
|
||
|
|
.spawn()
|
||
|
|
.expect("failed to execute process");
|
||
|
|
|
||
|
|
host_ncat
|
||
|
|
.stdin
|
||
|
|
.take()
|
||
|
|
.unwrap()
|
||
|
|
.write_all(MESSAGE_TO_GUEST.as_bytes())
|
||
|
|
.unwrap();
|
||
|
|
|
||
|
|
let cmd = format!("ncat --recv-only --vsock {HOST_CID} {host_port}; echo ''");
|
||
|
|
let guest_stdout = retry(|| vm.exec_in_guest(&cmd), NCAT_RETRIES).unwrap();
|
||
|
|
assert_eq!(guest_stdout.trim(), MESSAGE_TO_GUEST);
|
||
|
|
|
||
|
|
host_ncat.wait_with_timeout(SERVER_TIMEOUT).unwrap();
|
||
|
|
}
|
||
|
|
|
||
|
|
fn create_vu_config(cmd_type: CmdType, socket: &Path, cid: u32) -> VuConfig {
|
||
|
|
let socket_path = socket.to_str().unwrap();
|
||
|
|
println!("cid={cid}, socket={socket_path}");
|
||
|
|
match cmd_type {
|
||
|
|
CmdType::Device => VuConfig::new(cmd_type, "vsock").extra_args(vec![
|
||
|
|
"vsock".to_string(),
|
||
|
|
"--socket".to_string(),
|
||
|
|
socket_path.to_string(),
|
||
|
|
"--cid".to_string(),
|
||
|
|
cid.to_string(),
|
||
|
|
]),
|
||
|
|
CmdType::Devices => VuConfig::new(cmd_type, "vsock").extra_args(vec![
|
||
|
|
"--vsock".to_string(),
|
||
|
|
format!("vhost={},cid={}", socket_path, cid),
|
||
|
|
]),
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn vhost_user_host_to_guest() {
|
||
|
|
let guest_cid = generate_guest_cid();
|
||
|
|
let socket = NamedTempFile::new().unwrap();
|
||
|
|
|
||
|
|
let vu_config = create_vu_config(CmdType::Device, socket.path(), guest_cid);
|
||
|
|
let _vu_device = VhostUserBackend::new(vu_config).unwrap();
|
||
|
|
|
||
|
|
let config = Config::new().extra_args(vec![
|
||
|
|
"--vhost-user-vsock".to_string(),
|
||
|
|
socket.path().to_str().unwrap().to_string(),
|
||
|
|
]);
|
||
|
|
|
||
|
|
host_to_guest_connection(config, guest_cid);
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn vhost_user_host_to_guest_with_devices() {
|
||
|
|
let guest_cid = generate_guest_cid();
|
||
|
|
let socket = NamedTempFile::new().unwrap();
|
||
|
|
|
||
|
|
let vu_config = create_vu_config(CmdType::Devices, socket.path(), guest_cid);
|
||
|
|
let _vu_device = VhostUserBackend::new(vu_config).unwrap();
|
||
|
|
|
||
|
|
let config = Config::new().extra_args(vec![
|
||
|
|
"--vhost-user-vsock".to_string(),
|
||
|
|
socket.path().to_str().unwrap().to_string(),
|
||
|
|
]);
|
||
|
|
|
||
|
|
host_to_guest_connection(config, guest_cid);
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn vhost_user_guest_to_host() {
|
||
|
|
let guest_cid = generate_guest_cid();
|
||
|
|
let socket = NamedTempFile::new().unwrap();
|
||
|
|
|
||
|
|
let vu_config = create_vu_config(CmdType::Device, socket.path(), guest_cid);
|
||
|
|
let _vu_device = VhostUserBackend::new(vu_config).unwrap();
|
||
|
|
|
||
|
|
let config = Config::new().extra_args(vec![
|
||
|
|
"--vhost-user-vsock".to_string(),
|
||
|
|
socket.path().to_str().unwrap().to_string(),
|
||
|
|
]);
|
||
|
|
|
||
|
|
guest_to_host_connection(config);
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn vhost_user_guest_to_host_with_devices() {
|
||
|
|
let guest_cid = generate_guest_cid();
|
||
|
|
let socket = NamedTempFile::new().unwrap();
|
||
|
|
|
||
|
|
let vu_config = create_vu_config(CmdType::Devices, socket.path(), guest_cid);
|
||
|
|
let _vu_device = VhostUserBackend::new(vu_config).unwrap();
|
||
|
|
|
||
|
|
let config = Config::new().extra_args(vec![
|
||
|
|
"--vhost-user-vsock".to_string(),
|
||
|
|
socket.path().to_str().unwrap().to_string(),
|
||
|
|
]);
|
||
|
|
|
||
|
|
guest_to_host_connection(config);
|
||
|
|
}
|