Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions cloud-hypervisor/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -881,6 +881,11 @@ fn main() {

if cmd_arguments.get_flag("version") {
println!("{} {}", env!("CARGO_BIN_NAME"), env!("BUILD_VERSION"));
println!(
"vm-migration protocol versions v{}-v{}",
vm_migration::protocol::PRIOR_PROTOCOL_VERSION,
vm_migration::protocol::CURRENT_PROTOCOL_VERSION
);

if cmd_arguments.get_count("v") != 0 {
println!("Enabled features: {:?}", vmm::feature_list());
Expand Down
89 changes: 86 additions & 3 deletions vm-migration/src/protocol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,19 @@
//! Source->>Destination: Complete
//! Destination-->>Source: OK
//! ```
//!
//! ## Protocol Versioning
//!
//! `Start` carries the sender's migration protocol version.
//! A zeroed version field is treated as legacy protocol `v0`.
//!
//! The destination validates that version and replies with a plain `OK` or
//! `Error`.
//!
//! Only the current and immediately previous protocol versions are
//! supported. Compatibility is one-way, from older protocol versions
//! to newer ones.
//!

use std::io::{Read, Write};

Expand Down Expand Up @@ -124,11 +137,25 @@ pub enum Command {
KeepAlive,
}

pub type MigrationProtocolVersion = u16;

pub const CURRENT_PROTOCOL_VERSION: MigrationProtocolVersion = 0;
pub const PRIOR_PROTOCOL_VERSION: MigrationProtocolVersion =
CURRENT_PROTOCOL_VERSION.saturating_sub(1);

/// Encodes a sender protocol version into the `command_headers` bytes of a
/// `Start` request.
fn encode_sender_version(version: MigrationProtocolVersion) -> [u8; 6] {
let mut command_headers = [0; 6];
command_headers[..2].copy_from_slice(&version.to_le_bytes());
command_headers
}

#[repr(C)]
#[derive(Default, Copy, Clone, Immutable, IntoBytes, KnownLayout, TryFromBytes)]
pub struct Request {
command: Command,
padding: [u8; 6],
command_headers: [u8; 6],
length: u64, // Length of payload for command excluding the Request struct
}

Expand All @@ -142,7 +169,11 @@ impl Request {
}

pub fn start() -> Self {
Self::new(Command::Start, 0)
Self {
command: Command::Start,
command_headers: encode_sender_version(CURRENT_PROTOCOL_VERSION),
length: 0,
}
}

pub fn state(length: u64) -> Self {
Expand Down Expand Up @@ -181,6 +212,36 @@ impl Request {
self.length
}

pub fn command_headers(&self) -> &[u8; 6] {
&self.command_headers
}

/// Returns the sender protocol version carried by a `Start` request.
///
/// Only the first two bytes are used. The remaining bytes are ignored.
pub fn sender_protocol_version(&self) -> MigrationProtocolVersion {
u16::from_le_bytes([self.command_headers[0], self.command_headers[1]])
}

pub fn validate_start_protocol_version(
&self,
) -> Result<MigrationProtocolVersion, MigratableError> {
debug_assert_eq!(
self.command(),
Command::Start,
"validate_start_protocol_version() must only be called for Start requests",
);

let sender_version = self.sender_protocol_version();
if sender_version != PRIOR_PROTOCOL_VERSION && sender_version != CURRENT_PROTOCOL_VERSION {
return Err(MigratableError::MigrateReceive(anyhow!(
"Migration protocol version {sender_version} doesn't match supported versions: {PRIOR_PROTOCOL_VERSION}, {CURRENT_PROTOCOL_VERSION}"
)));
}

Ok(sender_version)
}

pub fn read_from(fd: &mut dyn Read) -> Result<Request, MigratableError> {
/// A byte buffer that matches `Self` in size and alignment to allow deserializing `Self` into.
#[repr(C, align(8))]
Expand Down Expand Up @@ -502,7 +563,29 @@ impl MemoryRangeTable {

#[cfg(test)]
mod unit_tests {
use crate::protocol::{MemoryRange, MemoryRangeTable};
use crate::protocol::{Command, MemoryRange, MemoryRangeTable, Request};

#[test]
fn test_start_request_ignores_residual_command_headers_bytes() {
let request = Request {
command: Command::Start,
command_headers: [1, 0, 0xaa, 0xbb, 0xcc, 0xdd],
length: 0,
};

assert_eq!(request.sender_protocol_version(), 1);
}

#[test]
fn test_validate_start_protocol_version_rejects_unsupported_version() {
let request = Request {
command: Command::Start,
command_headers: [2, 0, 0, 0, 0, 0],
length: 0,
};

request.validate_start_protocol_version().unwrap_err();
}

#[test]
fn test_memory_range_table_from_dirty_ranges_iter() {
Expand Down
7 changes: 6 additions & 1 deletion vmm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2179,7 +2179,11 @@ impl Vmm {

match state {
Established => match req.command() {
Command::Start => Ok(Started),
Command::Start => {
let migration_protocol_version = req.validate_start_protocol_version()?;
info!("Using migration protocol v{migration_protocol_version} on the receiver");
Ok(Started)
}
_ => invalid_command(),
},
Started => match req.command() {
Expand Down Expand Up @@ -2769,6 +2773,7 @@ impl Vmm {
&mut socket,
MigratableError::MigrateSend(anyhow!("Error starting migration (got bad response)")),
)?;
info!("Using migration protocol v{CURRENT_PROTOCOL_VERSION} on the sender");

return_if_cancelled_cb(&mut socket)?;

Expand Down
Loading