From aa98589bb4b7969b325e9919622b801f61286c10 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Wed, 28 Oct 2020 17:36:17 +0000 Subject: [PATCH] vm-migration: Add protocol documentation and data structures Add the documentation and basic implementation for supporting migration. Signed-off-by: Rob Bradford --- vm-migration/src/lib.rs | 5 + vm-migration/src/protocol.rs | 231 +++++++++++++++++++++++++++++++++++ 2 files changed, 236 insertions(+) create mode 100644 vm-migration/src/protocol.rs diff --git a/vm-migration/src/lib.rs b/vm-migration/src/lib.rs index 28e364799..e25ace17d 100644 --- a/vm-migration/src/lib.rs +++ b/vm-migration/src/lib.rs @@ -10,6 +10,8 @@ extern crate serde_derive; use thiserror::Error; +pub mod protocol; + #[derive(Error, Debug)] pub enum MigratableError { #[error("Failed to pause migratable component: {0}")] @@ -29,6 +31,9 @@ pub enum MigratableError { #[error("Failed to receive migratable component snapshot: {0}")] MigrateReceive(#[source] anyhow::Error), + + #[error("Socket error: {0}")] + MigrateSocket(#[source] std::io::Error), } /// A Pausable component can be paused and resumed. diff --git a/vm-migration/src/protocol.rs b/vm-migration/src/protocol.rs new file mode 100644 index 000000000..550e3c76c --- /dev/null +++ b/vm-migration/src/protocol.rs @@ -0,0 +1,231 @@ +// Copyright © 2020 Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 +// + +use crate::MigratableError; + +// Migration protocol +// 1: Source establishes communication with destination (file socket or TCP connection.) +// (The establishment is out of scope.) +// 2: Source -> Dest : send "start command" +// 3: Dest -> Source : sends "ok response" when read to accept state data +// 4: Source -> Dest : sends "state command" followed by state data, length +// in command is length of state data +// 5: Dest -> Source : sends "ok response" when ready to accept memory data +// 6: Source -> Dest : send "memory command" followed by table of u64 pairs (GPA, size) +// followed by the memory described in those pairs. +// !! length is size of table i.e. 16 * number of ranges !! +// 7: Dest -> Source : sends "ok response" when ready to accept more memory data +// 8..(n-2): Repeat steps 6 and 7 until source has no more memory to send +// (n-1): Source -> Dest : send "complete command" +// n: Dest -> Source: sends "ok response" + +// The destination can at any time send an "error response" to cancel +// The source can at any time send an "abandon request" to cancel + +use std::io::{Read, Write}; + +#[repr(u16)] +#[derive(Copy, Clone)] +pub enum Command { + Invalid, + Start, + State, + Memory, + Complete, + Abandon, +} + +impl Default for Command { + fn default() -> Self { + Self::Invalid + } +} + +trait AsBytes { + fn as_bytes(p: &T) -> &[u8] { + unsafe { + std::slice::from_raw_parts((p as *const T) as *const u8, std::mem::size_of::()) + } + } + + fn as_mut_bytes(p: &mut T) -> &mut [u8] { + unsafe { + std::slice::from_raw_parts_mut((p as *mut T) as *mut u8, std::mem::size_of::()) + } + } +} + +#[repr(C)] +#[derive(Default)] +pub struct Request { + command: Command, + padding: [u8; 6], + length: u64, // Length of payload for command excluding the Request struct +} + +impl Request { + pub fn new(command: Command, length: u64) -> Self { + Self { + command, + length, + ..Default::default() + } + } + + pub fn start() -> Self { + Self::new(Command::Start, 0) + } + + pub fn state(length: u64) -> Self { + Self::new(Command::State, length) + } + + pub fn memory(length: u64) -> Self { + Self::new(Command::Memory, length) + } + + pub fn complete() -> Self { + Self::new(Command::Complete, 0) + } + + pub fn abandon() -> Self { + Self::new(Command::Abandon, 0) + } + + pub fn command(&self) -> Command { + self.command + } + + pub fn length(&self) -> u64 { + self.length + } + + pub fn read_from(fd: &mut dyn Read) -> Result { + let mut request = Request::default(); + fd.read_exact(Self::as_mut_bytes(&mut request)) + .map_err(MigratableError::MigrateSocket)?; + + Ok(request) + } + + pub fn write_to(&self, fd: &mut dyn Write) -> Result<(), MigratableError> { + fd.write_all(Self::as_bytes(self)) + .map_err(MigratableError::MigrateSocket) + } +} + +impl AsBytes for Request {} + +#[repr(u16)] +#[derive(Copy, Clone, PartialEq)] +pub enum Status { + Invalid, + Ok, + Error, +} + +impl Default for Status { + fn default() -> Self { + Self::Invalid + } +} + +#[repr(C)] +#[derive(Default)] +pub struct Response { + status: Status, + padding: [u8; 6], + length: u64, // Length of payload for command excluding the Response struct +} + +impl Response { + pub fn new(status: Status, length: u64) -> Self { + Self { + status, + length, + ..Default::default() + } + } + + pub fn ok() -> Self { + Self::new(Status::Ok, 0) + } + + pub fn error() -> Self { + Self::new(Status::Error, 0) + } + + pub fn status(&self) -> Status { + self.status + } + + pub fn read_from(fd: &mut dyn Read) -> Result { + let mut response = Response::default(); + fd.read_exact(Self::as_mut_bytes(&mut response)) + .map_err(MigratableError::MigrateSocket)?; + + Ok(response) + } + + pub fn write_to(&self, fd: &mut dyn Write) -> Result<(), MigratableError> { + fd.write_all(Self::as_bytes(self)) + .map_err(MigratableError::MigrateSocket) + } +} + +impl AsBytes for Response {} + +#[repr(C)] +pub struct MemoryRange { + pub gpa: u64, + pub length: u64, +} + +#[derive(Default)] +pub struct MemoryRangeTable { + data: Vec, +} + +impl MemoryRangeTable { + pub fn regions(&self) -> &[MemoryRange] { + &self.data + } + + pub fn push(&mut self, range: MemoryRange) { + self.data.push(range) + } + + pub fn read_from(fd: &mut dyn Read, length: u64) -> Result { + assert!(length as usize % std::mem::size_of::() == 0); + + let mut data = Vec::with_capacity(length as usize / (std::mem::size_of::())); + unsafe { + data.set_len(length as usize / (std::mem::size_of::())); + } + fd.read_exact(unsafe { + std::slice::from_raw_parts_mut( + data.as_ptr() as *mut MemoryRange as *mut u8, + length as usize, + ) + }) + .map_err(MigratableError::MigrateSocket)?; + + Ok(Self { data }) + } + + pub fn length(&self) -> u64 { + (std::mem::size_of::() * self.data.len()) as u64 + } + + pub fn write_to(&self, fd: &mut dyn Write) -> Result<(), MigratableError> { + fd.write_all(unsafe { + std::slice::from_raw_parts( + self.data.as_ptr() as *const MemoryRange as *const u8, + self.length() as usize, + ) + }) + .map_err(MigratableError::MigrateSocket) + } +}