#[cfg(feature = "tee")]
use std::fs::File;
#[cfg(feature = "tee")]
use std::io::BufReader;
#[cfg(not(target_os = "windows"))]
use std::os::fd::RawFd;
use std::path::PathBuf;
use std::time::Duration;
#[cfg(feature = "tee")]
use serde::{Deserialize, Serialize};
#[cfg(feature = "blk")]
use crate::vmm_config::block::{BlockBuilder, BlockConfigError, BlockDeviceConfig};
use crate::vmm_config::external_kernel::ExternalKernel;
use crate::vmm_config::firmware::FirmwareConfig;
#[cfg(not(feature = "tee"))]
use crate::vmm_config::fs::*;
use crate::vmm_config::kernel_bundle::InitrdBundle;
use crate::vmm_config::kernel_bundle::{KernelBundle, KernelBundleError};
#[cfg(feature = "tee")]
use crate::vmm_config::kernel_bundle::{QbootBundle, QbootBundleError};
use crate::vmm_config::kernel_cmdline::{KernelCmdlineConfig, KernelCmdlineConfigError};
#[cfg(any(target_os = "linux", target_os = "windows"))]
use crate::vmm_config::machine_config::HostCpuId;
use crate::vmm_config::machine_config::{VmConfig, VmConfigError};
#[cfg(feature = "net")]
use crate::vmm_config::net::{NetBuilder, NetworkInterfaceConfig, NetworkInterfaceError};
use crate::vmm_config::vsock::*;
use crate::vstate::VcpuConfig;
#[cfg(feature = "gpu")]
use devices::virtio::display::DisplayInfo;
#[cfg(feature = "tee")]
use kbs_types::Tee;
#[cfg(feature = "gpu")]
use krun_display::DisplayBackend;
use utils::metrics::MetricsWriter;
type Result<E> = std::result::Result<(), E>;
#[cfg(target_os = "windows")]
pub use crate::vmm_config::vsock::TsiFlags;
#[cfg(not(target_os = "windows"))]
pub use devices::virtio::TsiFlags;
#[derive(Debug)]
pub enum Error {
InvalidJson,
KernelCmdline(KernelCmdlineConfigError),
#[cfg(feature = "tee")]
OpenTeeConfig(std::io::Error),
#[cfg(feature = "tee")]
ParseTeeConfig(serde_json::Error),
VmConfig(VmConfigError),
VsockDevice(VsockConfigError),
}
#[cfg(feature = "tee")]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TeeConfig {
pub workload_id: String,
pub cpus: u8,
pub ram_mib: usize,
pub tee: Tee,
pub tee_data: String,
pub attestation_url: String,
}
#[cfg(feature = "tee")]
impl Default for TeeConfig {
fn default() -> Self {
Self {
workload_id: "".to_string(),
cpus: 0,
ram_mib: 0,
tee: Tee::Sev,
tee_data: "".to_string(),
attestation_url: "".to_string(),
}
}
}
#[cfg(not(target_os = "windows"))]
pub struct SerialConsoleConfig {
pub input_fd: RawFd,
pub output_fd: RawFd,
}
#[cfg(not(target_os = "windows"))]
pub struct DefaultVirtioConsoleConfig {
pub input_fd: RawFd,
pub output_fd: RawFd,
pub err_fd: RawFd,
}
#[cfg(not(target_os = "windows"))]
pub enum VirtioConsoleConfigMode {
Autoconfigure(DefaultVirtioConsoleConfig),
Explicit(Vec<PortConfig>),
}
#[cfg(target_os = "windows")]
pub enum VirtioConsoleConfigMode {
Explicit(Vec<PortConfig>),
}
#[cfg(not(target_os = "windows"))]
pub enum PortConfig {
Tty {
name: String,
tty_fd: RawFd,
},
InOut {
name: String,
input_fd: RawFd,
output_fd: RawFd,
},
Custom {
name: String,
input: Box<dyn devices::virtio::port_io::PortInput + Send>,
output: Box<dyn devices::virtio::port_io::PortOutput + Send>,
},
}
#[cfg(target_os = "windows")]
pub enum PortConfig {
ConsoleOutputFile { path: PathBuf },
NamedPipe { name: String, pipe_name: String },
}
#[derive(Debug, Default, Clone, Eq, PartialEq)]
pub enum VsockConfig {
#[default]
Implicit,
Explicit { tsi_flags: TsiFlags },
Disabled,
}
pub struct VmResources {
vm_config: VmConfig,
#[cfg(any(target_os = "linux", target_os = "windows"))]
pub vcpu_affinity: Option<Vec<HostCpuId>>,
pub firmware_config: Option<FirmwareConfig>,
pub kernel_cmdline: KernelCmdlineConfig,
pub kernel_bundle: Option<KernelBundle>,
pub external_kernel: Option<ExternalKernel>,
#[cfg(feature = "tee")]
pub qboot_bundle: Option<QbootBundle>,
pub initrd_bundle: Option<InitrdBundle>,
#[cfg(not(feature = "tee"))]
pub fs: Vec<FsDeviceConfig>,
#[cfg(not(any(feature = "tee", feature = "aws-nitro")))]
pub custom_fs: Vec<CustomFsDeviceConfig>,
pub vsock: VsockBuilder,
#[cfg(feature = "blk")]
pub block: BlockBuilder,
#[cfg(feature = "net")]
pub net: NetBuilder,
#[cfg(feature = "tee")]
pub tee_config: TeeConfig,
pub gpu_virgl_flags: Option<u32>,
pub gpu_shm_size: Option<usize>,
#[cfg(feature = "gpu")]
pub display_backend: Option<DisplayBackend<'static>>,
#[cfg(feature = "gpu")]
pub displays: Vec<DisplayInfo>,
#[cfg(feature = "input")]
pub input_backends: Vec<(
krun_input::InputConfigBackend<'static>,
krun_input::InputEventProviderBackend<'static>,
)>,
#[cfg(feature = "snd")]
pub snd_device: bool,
pub console_output: Option<PathBuf>,
pub smbios_oem_strings: Option<Vec<String>>,
pub nested_enabled: bool,
pub split_irqchip: bool,
pub metrics: MetricsWriter,
pub enable_balloon: bool,
#[cfg(not(feature = "tee"))]
pub mem_device: Option<std::sync::Arc<std::sync::Mutex<devices::virtio::Mem>>>,
#[cfg(not(feature = "tee"))]
pub cpu_device: Option<std::sync::Arc<std::sync::Mutex<devices::virtio::Cpu>>>,
pub balloon_stats_interval: Option<Duration>,
pub enable_rng: bool,
pub enable_msb_metrics: bool,
pub disable_implicit_console: bool,
pub kernel_console: Option<String>,
#[cfg(not(target_os = "windows"))]
pub serial_consoles: Vec<SerialConsoleConfig>,
pub virtio_consoles: Vec<VirtioConsoleConfigMode>,
}
impl Default for VmResources {
fn default() -> Self {
Self {
vm_config: VmConfig::default(),
#[cfg(any(target_os = "linux", target_os = "windows"))]
vcpu_affinity: None,
firmware_config: None,
kernel_cmdline: KernelCmdlineConfig::default(),
kernel_bundle: None,
external_kernel: None,
#[cfg(feature = "tee")]
qboot_bundle: None,
initrd_bundle: None,
#[cfg(not(feature = "tee"))]
fs: Vec::new(),
#[cfg(not(any(feature = "tee", feature = "aws-nitro")))]
custom_fs: Vec::new(),
vsock: VsockBuilder::default(),
#[cfg(feature = "blk")]
block: BlockBuilder::default(),
#[cfg(feature = "net")]
net: NetBuilder::default(),
#[cfg(feature = "tee")]
tee_config: TeeConfig::default(),
gpu_virgl_flags: None,
gpu_shm_size: None,
#[cfg(feature = "gpu")]
display_backend: None,
#[cfg(feature = "gpu")]
displays: Vec::new(),
#[cfg(feature = "input")]
input_backends: Vec::new(),
#[cfg(feature = "snd")]
snd_device: false,
console_output: None,
smbios_oem_strings: None,
nested_enabled: false,
split_irqchip: false,
metrics: MetricsWriter::default(),
enable_balloon: true,
#[cfg(not(feature = "tee"))]
mem_device: None,
#[cfg(not(feature = "tee"))]
cpu_device: None,
balloon_stats_interval: Some(Duration::from_secs(1)),
enable_rng: true,
enable_msb_metrics: true,
disable_implicit_console: false,
kernel_console: None,
#[cfg(not(target_os = "windows"))]
serial_consoles: Vec::new(),
virtio_consoles: Vec::new(),
}
}
}
impl VmResources {
pub fn vcpu_config(&self) -> VcpuConfig {
let vcpu_count = self.vm_config().vcpu_count.unwrap();
VcpuConfig {
vcpu_count,
max_vcpu_count: self.vm_config().max_vcpu_count.unwrap_or(vcpu_count),
ht_enabled: self.vm_config().ht_enabled.unwrap(),
cpu_template: self.vm_config().cpu_template,
}
}
pub fn vm_config(&self) -> &VmConfig {
&self.vm_config
}
pub fn set_vm_config(&mut self, machine_config: &VmConfig) -> Result<VmConfigError> {
if machine_config.vcpu_count == Some(0) {
return Err(VmConfigError::InvalidVcpuCount);
}
if machine_config.mem_size_mib == Some(0) {
return Err(VmConfigError::InvalidMemorySize);
}
let ht_enabled = machine_config
.ht_enabled
.unwrap_or_else(|| self.vm_config.ht_enabled.unwrap());
let vcpu_count_value = machine_config
.vcpu_count
.unwrap_or_else(|| self.vm_config.vcpu_count.unwrap());
if ht_enabled && vcpu_count_value > 1 && vcpu_count_value % 2 == 1 {
return Err(VmConfigError::InvalidVcpuCount);
}
if let Some(max_vcpu_count) = machine_config.max_vcpu_count {
if max_vcpu_count < vcpu_count_value
|| max_vcpu_count > crate::vmm_config::machine_config::MAX_SUPPORTED_VCPUS
{
return Err(VmConfigError::InvalidMaxVcpuCount);
}
if ht_enabled && max_vcpu_count > 1 && max_vcpu_count % 2 == 1 {
return Err(VmConfigError::InvalidMaxVcpuCount);
}
#[cfg(any(target_arch = "riscv64", feature = "tee"))]
if max_vcpu_count > vcpu_count_value {
return Err(VmConfigError::MaxCapacityUnsupported);
}
}
if let Some(max_mem_size_mib) = machine_config.max_mem_size_mib {
let mem_size_mib = machine_config
.mem_size_mib
.unwrap_or_else(|| self.vm_config.mem_size_mib.unwrap());
if max_mem_size_mib < mem_size_mib {
return Err(VmConfigError::InvalidMaxMemorySize);
}
}
self.vm_config.vcpu_count = Some(vcpu_count_value);
self.vm_config.ht_enabled = Some(ht_enabled);
self.vm_config.max_vcpu_count = machine_config.max_vcpu_count;
self.vm_config.max_mem_size_mib = machine_config.max_mem_size_mib;
if machine_config.mem_size_mib.is_some() {
self.vm_config.mem_size_mib = machine_config.mem_size_mib;
}
let memory_total_bytes = self
.vm_config
.mem_size_mib
.unwrap_or(128)
.saturating_mul(1024)
.saturating_mul(1024) as u64;
self.metrics.set_memory_total_bytes(memory_total_bytes);
if machine_config.cpu_template.is_some() {
self.vm_config.cpu_template = machine_config.cpu_template;
}
Ok(())
}
pub fn set_kernel_cmdline(
&mut self,
kernel_cmdline_cfg: KernelCmdlineConfig,
) -> Result<KernelCmdlineConfigError> {
self.kernel_cmdline = kernel_cmdline_cfg;
Ok(())
}
pub fn kernel_bundle(&self) -> Option<&KernelBundle> {
self.kernel_bundle.as_ref()
}
pub fn set_kernel_bundle(&mut self, kernel_bundle: KernelBundle) -> Result<KernelBundleError> {
let page_size = utils::page_size();
if kernel_bundle.host_addr == 0 || (kernel_bundle.host_addr as usize) & (page_size - 1) != 0
{
return Err(KernelBundleError::InvalidHostAddress);
}
if (kernel_bundle.guest_addr as usize) & (page_size - 1) != 0 {
return Err(KernelBundleError::InvalidGuestAddress);
}
self.kernel_bundle = Some(kernel_bundle);
Ok(())
}
pub fn external_kernel(&self) -> Option<&ExternalKernel> {
self.external_kernel.as_ref()
}
pub fn set_external_kernel(&mut self, external_kernel: ExternalKernel) {
self.external_kernel = Some(external_kernel);
}
pub fn set_firmware_config(&mut self, firmware_config: FirmwareConfig) {
self.firmware_config = Some(firmware_config);
}
#[cfg(feature = "tee")]
pub fn qboot_bundle(&self) -> Option<&QbootBundle> {
self.qboot_bundle.as_ref()
}
#[cfg(feature = "tee")]
pub fn set_qboot_bundle(&mut self, qboot_bundle: QbootBundle) -> Result<QbootBundleError> {
if qboot_bundle.size != 0x10000 {
return Err(QbootBundleError::InvalidSize);
}
self.qboot_bundle = Some(qboot_bundle);
Ok(())
}
pub fn initrd_bundle(&self) -> Option<&InitrdBundle> {
self.initrd_bundle.as_ref()
}
pub fn set_initrd_bundle(&mut self, initrd_bundle: InitrdBundle) -> Result<KernelBundleError> {
self.initrd_bundle = Some(initrd_bundle);
Ok(())
}
#[cfg(not(feature = "tee"))]
pub fn add_fs_device(&mut self, config: FsDeviceConfig) {
self.fs.push(config)
}
#[cfg(feature = "blk")]
pub fn add_block_device(&mut self, config: BlockDeviceConfig) -> Result<BlockConfigError> {
self.block.insert(config, self.metrics.clone())
}
#[cfg(feature = "blk")]
pub fn add_block_device_with_writeback_limit(
&mut self,
config: BlockDeviceConfig,
writeback_limit_bytes: Option<u64>,
) -> Result<BlockConfigError> {
self.block
.insert_with_writeback_limit(config, writeback_limit_bytes, self.metrics.clone())
}
pub fn set_vsock_device(&mut self, config: VsockDeviceConfig) -> Result<VsockConfigError> {
self.vsock.insert(config)
}
pub fn set_gpu_virgl_flags(&mut self, virgl_flags: u32) {
self.gpu_virgl_flags = Some(virgl_flags);
}
pub fn set_gpu_shm_size(&mut self, shm_size: usize) {
self.gpu_shm_size = Some(shm_size);
}
#[cfg(feature = "snd")]
pub fn set_snd_device(&mut self, enabled: bool) {
self.snd_device = enabled;
}
pub fn set_console_output(&mut self, console_output: PathBuf) {
self.console_output = Some(console_output);
}
#[cfg(feature = "net")]
pub fn add_network_interface(
&mut self,
config: NetworkInterfaceConfig,
) -> Result<NetworkInterfaceError> {
self.net.insert(config)
}
#[cfg(feature = "tee")]
pub fn tee_config(&self) -> &TeeConfig {
&self.tee_config
}
#[cfg(feature = "tee")]
pub fn set_tee_config(&mut self, filepath: PathBuf) -> Result<Error> {
let file = File::open(filepath.as_path()).map_err(Error::OpenTeeConfig)?;
let reader = BufReader::new(file);
let tee_config: TeeConfig =
serde_json::from_reader(reader).map_err(Error::ParseTeeConfig)?;
self.set_vm_config(&VmConfig {
vcpu_count: Some(tee_config.cpus),
mem_size_mib: Some(tee_config.ram_mib),
max_vcpu_count: None,
max_mem_size_mib: None,
ht_enabled: Some(false),
cpu_template: None,
})
.map_err(Error::VmConfig)?;
self.tee_config = tee_config;
Ok(())
}
}
#[cfg(all(test, not(target_os = "windows")))]
mod tests {
#[cfg(feature = "gpu")]
use crate::resources::DisplayBackendConfig;
use crate::resources::VmResources;
use crate::vmm_config::machine_config::{CpuFeaturesTemplate, VmConfig, VmConfigError};
use crate::vmm_config::vsock::tests::{default_config, TempSockFile};
use crate::vstate::VcpuConfig;
use utils::tempfile::TempFile;
fn default_vm_resources() -> VmResources {
VmResources::default()
}
#[test]
fn test_vcpu_config() {
let vm_resources = default_vm_resources();
let expected_vcpu_config = VcpuConfig {
vcpu_count: vm_resources.vm_config().vcpu_count.unwrap(),
max_vcpu_count: vm_resources.vm_config().vcpu_count.unwrap(),
ht_enabled: vm_resources.vm_config().ht_enabled.unwrap(),
cpu_template: vm_resources.vm_config().cpu_template,
};
let vcpu_config = vm_resources.vcpu_config();
assert_eq!(vcpu_config, expected_vcpu_config);
}
#[test]
fn test_vm_config() {
let vm_resources = default_vm_resources();
let expected_vm_cfg = VmConfig::default();
assert_eq!(vm_resources.vm_config(), &expected_vm_cfg);
}
#[test]
fn test_set_vm_config() {
let mut vm_resources = default_vm_resources();
let mut aux_vm_config = VmConfig {
vcpu_count: Some(32),
mem_size_mib: Some(512),
max_vcpu_count: None,
max_mem_size_mib: None,
ht_enabled: Some(true),
cpu_template: Some(CpuFeaturesTemplate::T2),
};
assert_ne!(vm_resources.vm_config, aux_vm_config);
vm_resources.set_vm_config(&aux_vm_config).unwrap();
assert_eq!(vm_resources.vm_config, aux_vm_config);
aux_vm_config.vcpu_count = Some(0);
assert_eq!(
vm_resources.set_vm_config(&aux_vm_config),
Err(VmConfigError::InvalidVcpuCount)
);
aux_vm_config.vcpu_count = Some(33);
assert_eq!(
vm_resources.set_vm_config(&aux_vm_config),
Err(VmConfigError::InvalidVcpuCount)
);
aux_vm_config.vcpu_count = Some(32);
aux_vm_config.mem_size_mib = Some(0);
assert_eq!(
vm_resources.set_vm_config(&aux_vm_config),
Err(VmConfigError::InvalidMemorySize)
);
}
#[test]
fn test_set_vm_config_max_capacity() {
let mut vm_resources = default_vm_resources();
let mut vm_config = VmConfig {
vcpu_count: Some(2),
mem_size_mib: Some(1024),
max_vcpu_count: Some(8),
max_mem_size_mib: Some(8192),
ht_enabled: Some(false),
cpu_template: None,
};
vm_resources.set_vm_config(&vm_config).unwrap();
let vcpu_config = vm_resources.vcpu_config();
assert_eq!(vcpu_config.vcpu_count, 2);
assert_eq!(vcpu_config.max_vcpu_count, 8);
vm_config.max_vcpu_count = None;
vm_config.max_mem_size_mib = None;
vm_resources.set_vm_config(&vm_config).unwrap();
assert_eq!(vm_resources.vcpu_config().max_vcpu_count, 2);
vm_config.max_vcpu_count = Some(1);
assert_eq!(
vm_resources.set_vm_config(&vm_config),
Err(VmConfigError::InvalidMaxVcpuCount)
);
vm_config.max_vcpu_count = Some(65);
assert_eq!(
vm_resources.set_vm_config(&vm_config),
Err(VmConfigError::InvalidMaxVcpuCount)
);
vm_config.max_vcpu_count = Some(3);
vm_config.ht_enabled = Some(true);
assert_eq!(
vm_resources.set_vm_config(&vm_config),
Err(VmConfigError::InvalidMaxVcpuCount)
);
vm_config.ht_enabled = Some(false);
vm_config.max_vcpu_count = Some(8);
vm_config.max_mem_size_mib = Some(512);
assert_eq!(
vm_resources.set_vm_config(&vm_config),
Err(VmConfigError::InvalidMaxMemorySize)
);
}
#[test]
fn test_set_vsock_device() {
let mut vm_resources = default_vm_resources();
let tmp_sock_file = TempSockFile::new(TempFile::new().unwrap());
let new_vsock_cfg = default_config(&tmp_sock_file);
assert!(vm_resources.vsock.get().is_none());
vm_resources
.set_vsock_device(new_vsock_cfg.clone())
.unwrap();
let actual_vsock_cfg = vm_resources.vsock.get().unwrap();
assert_eq!(
actual_vsock_cfg.lock().unwrap().id(),
&new_vsock_cfg.vsock_id
);
}
}