mirror of
https://github.com/cloud-hypervisor/cloud-hypervisor.git
synced 2026-08-05 02:19:16 +00:00
vmm, ch-remote: Allow VFIO fd substitution at receive-migration
The VmConfig that arrives over the migration stream carries the source's device paths and stale FDs, none of which are usable on the destination. A received VFIO device therefore needs fresh descriptors supplied with the request. VmReceiveMigrationData gains vfio_fds, pairing each device id with a cdev FD, and iommufd_fd for the backing iommufd, both arriving over SCM_RIGHTS. Once the received VmConfig is available, each named device's path or FD is swapped for the received one and the iommufd is installed before the VM is built, so the device comes up FD backed. The request is rejected when a substituted device lacks the iommufd backend, when an id is unknown or repeated, or when a device names no replacement in vfio_fds. These checks run against the migrated VmConfig once it has been received. ch-remote gains the vfio_fds and iommufd_fd options and forwards the descriptors over SCM_RIGHTS. The D-Bus transport cannot carry file descriptors and drops them. Signed-off-by: Saravanan D <saravanand@crusoe.ai>
This commit is contained in:
+190
-5
@@ -962,13 +962,13 @@ impl Vmm {
|
||||
)))
|
||||
};
|
||||
|
||||
let mode = receive_data_migration.memory_mode;
|
||||
let mut configure_vm =
|
||||
|socket: &mut SocketStream,
|
||||
memory_files: HashMap<u32, File>|
|
||||
-> result::Result<ReceiveMigrationConfiguredData, MigratableError> {
|
||||
let shared_backing = !memory_files.is_empty();
|
||||
let memory_manager = self.vm_receive_config(req, socket, memory_files, mode)?;
|
||||
let memory_manager =
|
||||
self.vm_receive_config(req, socket, memory_files, receive_data_migration)?;
|
||||
let guest_memory = memory_manager.lock().unwrap().guest_memory();
|
||||
// Create the additional-connection receiver even in the single-connection case.
|
||||
// At this point the receiver does not know whether the sender will use extra TCP
|
||||
@@ -1148,11 +1148,13 @@ impl Vmm {
|
||||
req: &Request,
|
||||
socket: &mut T,
|
||||
existing_memory_files: HashMap<u32, File>,
|
||||
mode: MigrationMode,
|
||||
receive_data_migration: &VmReceiveMigrationData,
|
||||
) -> result::Result<Arc<Mutex<MemoryManager>>, MigratableError>
|
||||
where
|
||||
T: Read,
|
||||
{
|
||||
let mode = receive_data_migration.memory_mode;
|
||||
|
||||
// Read in config data along with memory manager data
|
||||
let mut data: Vec<u8> = Vec::new();
|
||||
data.resize_with(req.length() as usize, Default::default);
|
||||
@@ -1164,6 +1166,14 @@ impl Vmm {
|
||||
.context("Error deserialising config")
|
||||
.map_err(MigratableError::MigrateReceive)?;
|
||||
|
||||
// Mirrors the vm_restore handling of RestoreConfig.vfio_fds. The
|
||||
// received VmConfig carries the source's device paths or stale FDs,
|
||||
// neither of which is usable on this host.
|
||||
receive_data_migration
|
||||
.validate_vfio_fds(&vm_migration_config.vm_config.lock().unwrap())
|
||||
.map_err(|e| MigratableError::MigrateReceive(e.into()))?;
|
||||
apply_vfio_fds_to_vm_config(receive_data_migration, &vm_migration_config.vm_config);
|
||||
|
||||
// Eager prefault populates memory before UFFD is registered, so those
|
||||
// pages never fault and are never served. Reject postcopy+prefault
|
||||
// rather than serve stale data.
|
||||
@@ -2193,6 +2203,44 @@ fn apply_landlock(vm_config: &mut VmConfig) -> result::Result<(), LandlockError>
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// For each matched DeviceConfig.id, swap the saved path or stale FD for the
|
||||
// cdev FD received with the request, and install the fresh iommufd FD backing
|
||||
// them. The values in the migrated VmConfig are stale. validate_vfio_fds has
|
||||
// already confirmed the ids match and the iommufd FD is present.
|
||||
fn apply_vfio_fds_to_vm_config(
|
||||
receive_data_migration: &VmReceiveMigrationData,
|
||||
vm_config: &Arc<Mutex<VmConfig>>,
|
||||
) {
|
||||
let vfio_fds = receive_data_migration
|
||||
.vfio_fds
|
||||
.as_deref()
|
||||
.unwrap_or_default();
|
||||
if vfio_fds.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut config = vm_config.lock().unwrap();
|
||||
if let Some(devices) = config.devices.as_mut() {
|
||||
for v in vfio_fds {
|
||||
for device in devices.iter_mut() {
|
||||
if device.pci_common.id.as_deref() == Some(v.id.as_str()) {
|
||||
device.path = None;
|
||||
device.fd = v.fd;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let iommufd_fd = receive_data_migration
|
||||
.iommufd_fd
|
||||
.expect("receive-migration validated an iommufd FD accompanies vfio_fds");
|
||||
config
|
||||
.platform
|
||||
.as_mut()
|
||||
.expect("receive-migration validated iommufd=on, so a platform exists")
|
||||
.iommufd_fd = Some(iommufd_fd);
|
||||
}
|
||||
|
||||
impl RequestHandler for Vmm {
|
||||
fn vm_create(&mut self, config: Box<VmConfig>) -> result::Result<(), VmError> {
|
||||
match &self.vm {
|
||||
@@ -3299,12 +3347,13 @@ mod unit_tests {
|
||||
use arch::CpuProfile;
|
||||
|
||||
use super::*;
|
||||
use crate::config::RestoredVfioConfig;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
use crate::vm_config::DebugConsoleConfig;
|
||||
use crate::vm_config::{
|
||||
CommonConsoleConfig, ConsoleConfig, ConsoleOutputMode, CoreScheduling, CpuFeatures,
|
||||
CpusConfig, HotplugMethod, MemoryConfig, PayloadConfig, PciDeviceCommonConfig, RngConfig,
|
||||
SerialConfig,
|
||||
CpusConfig, DeviceConfig, HotplugMethod, MemoryConfig, PayloadConfig,
|
||||
PciDeviceCommonConfig, PlatformConfig, RngConfig, SerialConfig,
|
||||
};
|
||||
|
||||
fn create_dummy_vmm() -> Vmm {
|
||||
@@ -3843,4 +3892,140 @@ mod unit_tests {
|
||||
vsock_config
|
||||
);
|
||||
}
|
||||
|
||||
fn vm_config_with_vfio_devices(ids: &[&str], iommufd: bool) -> Arc<Mutex<VmConfig>> {
|
||||
let mut config = *create_dummy_vm_config();
|
||||
let platform = if iommufd { "iommufd=on" } else { "iommufd=off" };
|
||||
config.platform = Some(PlatformConfig::parse(platform).unwrap());
|
||||
config.devices = Some(
|
||||
ids.iter()
|
||||
.map(|id| DeviceConfig {
|
||||
pci_common: PciDeviceCommonConfig {
|
||||
id: Some((*id).to_owned()),
|
||||
..Default::default()
|
||||
},
|
||||
path: Some(PathBuf::from(format!("/sys/bus/pci/devices/{id}"))),
|
||||
fd: None,
|
||||
x_nv_gpudirect_clique: None,
|
||||
x_exclude_mmap_bars: Vec::new(),
|
||||
})
|
||||
.collect(),
|
||||
);
|
||||
Arc::new(Mutex::new(config))
|
||||
}
|
||||
|
||||
fn receive_data(
|
||||
vfio_fds: Option<Vec<RestoredVfioConfig>>,
|
||||
iommufd_fd: Option<i32>,
|
||||
) -> VmReceiveMigrationData {
|
||||
VmReceiveMigrationData {
|
||||
receiver_url: "tcp:127.0.0.1:4321".to_string(),
|
||||
tls_dir: None,
|
||||
memory_mode: MigrationMode::default(),
|
||||
vfio_fds,
|
||||
iommufd_fd,
|
||||
}
|
||||
}
|
||||
|
||||
fn vfio_fd(id: &str, fd: i32) -> RestoredVfioConfig {
|
||||
RestoredVfioConfig {
|
||||
id: id.to_owned(),
|
||||
fd: Some(fd),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_apply_vfio_fds_empty_is_noop() {
|
||||
let vm_config = vm_config_with_vfio_devices(&["vfio0"], true);
|
||||
apply_vfio_fds_to_vm_config(&receive_data(None, None), &vm_config);
|
||||
let config = vm_config.lock().unwrap();
|
||||
let device = &config.devices.as_ref().unwrap()[0];
|
||||
assert!(device.path.is_some());
|
||||
assert!(device.fd.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_apply_vfio_fds_swaps_path_for_fd() {
|
||||
let vm_config = vm_config_with_vfio_devices(&["vfio0", "vfio1"], true);
|
||||
let data = receive_data(Some(vec![vfio_fd("vfio1", 5)]), Some(6));
|
||||
apply_vfio_fds_to_vm_config(&data, &vm_config);
|
||||
let config = vm_config.lock().unwrap();
|
||||
let devices = config.devices.as_ref().unwrap();
|
||||
assert!(devices[0].path.is_some());
|
||||
assert!(devices[0].fd.is_none());
|
||||
assert!(devices[1].path.is_none());
|
||||
assert_eq!(devices[1].fd, Some(5));
|
||||
assert_eq!(config.platform.as_ref().unwrap().iommufd_fd, Some(6));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_validate_vfio_fds_requires_iommufd_fd() {
|
||||
let vm_config = vm_config_with_vfio_devices(&["vfio0"], true);
|
||||
let data = receive_data(Some(vec![vfio_fd("vfio0", 5)]), None);
|
||||
data.validate_vfio_fds(&vm_config.lock().unwrap())
|
||||
.unwrap_err();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_validate_vfio_fds_unknown_id_fails() {
|
||||
// vfio0 is covered so coverage passes, the extra bogus id is what the
|
||||
// id match must reject.
|
||||
let vm_config = vm_config_with_vfio_devices(&["vfio0"], true);
|
||||
let data = receive_data(
|
||||
Some(vec![vfio_fd("vfio0", 5), vfio_fd("missing", 6)]),
|
||||
Some(7),
|
||||
);
|
||||
data.validate_vfio_fds(&vm_config.lock().unwrap())
|
||||
.unwrap_err();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_validate_vfio_fds_duplicate_id_fails() {
|
||||
let vm_config = vm_config_with_vfio_devices(&["vfio0"], true);
|
||||
let data = receive_data(
|
||||
Some(vec![vfio_fd("vfio0", 5), vfio_fd("vfio0", 6)]),
|
||||
Some(7),
|
||||
);
|
||||
data.validate_vfio_fds(&vm_config.lock().unwrap())
|
||||
.unwrap_err();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_validate_vfio_fds_requires_iommufd_backend() {
|
||||
let vm_config = vm_config_with_vfio_devices(&["vfio0"], false);
|
||||
let data = receive_data(Some(vec![vfio_fd("vfio0", 5)]), Some(6));
|
||||
data.validate_vfio_fds(&vm_config.lock().unwrap())
|
||||
.unwrap_err();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_validate_vfio_fds_stale_fd_needs_replacement() {
|
||||
let vm_config = vm_config_with_vfio_devices(&["vfio0"], true);
|
||||
{
|
||||
let mut config = vm_config.lock().unwrap();
|
||||
let device = &mut config.devices.as_mut().unwrap()[0];
|
||||
device.path = None;
|
||||
device.fd = Some(-1);
|
||||
}
|
||||
receive_data(None, None)
|
||||
.validate_vfio_fds(&vm_config.lock().unwrap())
|
||||
.unwrap_err();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_validate_vfio_fds_path_based_needs_replacement() {
|
||||
// A path based device carries its source path, which is unusable on
|
||||
// the destination, so it must be replaced in vfio_fds as well.
|
||||
let vm_config = vm_config_with_vfio_devices(&["vfio0"], true);
|
||||
receive_data(None, None)
|
||||
.validate_vfio_fds(&vm_config.lock().unwrap())
|
||||
.unwrap_err();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_validate_vfio_fds_all_substituted_ok() {
|
||||
let vm_config = vm_config_with_vfio_devices(&["vfio0"], true);
|
||||
let data = receive_data(Some(vec![vfio_fd("vfio0", 5)]), Some(6));
|
||||
data.validate_vfio_fds(&vm_config.lock().unwrap()).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user