mimobox-vm 0.1.0-alpha

MimoBox microVM sandbox backend using Linux KVM
Documentation
#![cfg(all(target_os = "linux", feature = "kvm"))]

#[cfg(any(debug_assertions, feature = "boot-profile"))]
use super::profile::{BootProfile, parse_guest_boot_time_line};
use super::*;

mod serial;
mod vsock;
mod vsock_channel;

#[cfg(any(debug_assertions, feature = "boot-profile"))]
pub(in crate::kvm) use self::serial::SERIAL_BOOT_TIME_PREFIX;
#[allow(unused_imports)]
pub(in crate::kvm) use self::serial::{
    CommandResponse, FsResult, I8042_COMMAND_REG, I8042_PORT_B_PIT_TICK, I8042_PORT_B_REG,
    I8042_RESET_CMD, MAX_FS_TRANSFER_BYTES, PCI_CONFIG_ADDRESS_REG, PCI_CONFIG_DATA_REG_END,
    PCI_CONFIG_DATA_REG_START, SERIAL_EXEC_PREFIX, SERIAL_EXECS_PREFIX, SERIAL_FS_READ_PREFIX,
    SERIAL_FS_WRITE_PREFIX, SERIAL_HTTP_REQUEST_PREFIX, SERIAL_HTTPRESP_BODY_PREFIX,
    SERIAL_HTTPRESP_END_PREFIX, SERIAL_HTTPRESP_ERROR_PREFIX, SERIAL_HTTPRESP_HEADERS_PREFIX,
    SERIAL_PONG_LINE, SERIAL_READY_LINE, SerialDevice, SerialFrame, SerialProtocolResult,
    SerialResponseCollector, build_guest_command, build_guest_exec_payload, encode_command_payload,
    encode_fs_read_payload, encode_fs_write_payload, encode_ping_payload, parse_serial_line,
    preview_serial_output,
};
pub(in crate::kvm) use self::vsock::{VsockMmioAction, VsockMmioDevice, activate_vhost_backend};
pub(in crate::kvm) use self::vsock_channel::VsockCommandChannel;

pub(in crate::kvm) use self::serial::take_serial_frame;
use self::serial::{
    PIC_MASTER_COMMAND_REG, PIC_MASTER_DATA_REG, PIC_SLAVE_COMMAND_REG, PIC_SLAVE_DATA_REG,
    PIT_CHANNEL0_DATA_REG, PIT_MODE_COMMAND_REG, RTC_DATA_REG, RTC_INDEX_REG, SERIAL_PORT_COM1,
    SERIAL_PORT_LAST,
};

pub(super) fn is_serial_port(port: u16) -> bool {
    (SERIAL_PORT_COM1..=SERIAL_PORT_LAST).contains(&port)
}

pub(super) fn is_boot_legacy_pio_port(port: u16) -> bool {
    matches!(
        port,
        PIC_MASTER_COMMAND_REG | PIC_MASTER_DATA_REG | PIT_CHANNEL0_DATA_REG
            ..=PIT_MODE_COMMAND_REG
                | RTC_INDEX_REG
                | RTC_DATA_REG
                | PIC_SLAVE_COMMAND_REG
                | PIC_SLAVE_DATA_REG
    )
}

pub(super) fn emulate_boot_legacy_pio_read(port: u16, data: &mut [u8]) -> bool {
    if !is_boot_legacy_pio_port(port) {
        return false;
    }

    data.fill(0);
    true
}

#[cfg(any(debug_assertions, feature = "boot-profile"))]
#[allow(clippy::too_many_arguments)]
pub(super) fn handle_serial_write(
    serial_device: &mut SerialDevice,
    serial_buffer: &mut Vec<u8>,
    guest_ready: &mut bool,
    boot_profile: &mut BootProfile,
    port: u16,
    data: &[u8],
    frame_buffer: &mut Vec<u8>,
    response: Option<&mut SerialResponseCollector>,
) -> Result<Option<SerialProtocolResult>, MicrovmError> {
    let mut response = response;

    for &value in data {
        if let Some(tx_byte) = serial_device.write(port, value)? {
            serial_buffer.push(tx_byte);
            if tx_byte != b'\r' {
                frame_buffer.push(tx_byte);
            }

            while let Some(frame) = take_serial_frame(frame_buffer)? {
                match frame {
                    SerialFrame::Line(line) => {
                        if boot_profile.should_parse_guest_line()
                            && parse_guest_boot_time_line(&line, boot_profile)
                        {
                            continue;
                        }
                        if line == SERIAL_READY_LINE {
                            *guest_ready = true;
                            boot_profile.mark_boot_ready();
                            continue;
                        }
                        if line == SERIAL_PONG_LINE {
                            return Ok(Some(SerialProtocolResult::PingPong));
                        }

                        if let Some(response) = response.as_deref_mut() {
                            match response {
                                SerialResponseCollector::Command(command_response) => {
                                    if let Some(result) =
                                        parse_serial_line(&line, command_response)?
                                    {
                                        return Ok(Some(SerialProtocolResult::Command(result)));
                                    }
                                }
                                SerialResponseCollector::Fs => {
                                    if line.starts_with("OUTPUT:") || line.starts_with("EXIT:") {
                                        return Err(MicrovmError::Backend(format!(
                                            "unexpected serial line while waiting for FSRESULT: {line}"
                                        )));
                                    }
                                }
                                SerialResponseCollector::Http => {
                                    if line.starts_with("OUTPUT:") || line.starts_with("EXIT:") {
                                        return Err(MicrovmError::Backend(format!(
                                            "unexpected serial line while waiting for HTTP response: {line}"
                                        )));
                                    }
                                }
                            }
                        }
                    }
                    SerialFrame::FsResult(fs_result) => {
                        if let Some(response) = response.as_deref_mut() {
                            match response {
                                SerialResponseCollector::Command(_) => {
                                    return Err(MicrovmError::Backend(
                                        "unexpected FSRESULT frame during command execution".into(),
                                    ));
                                }
                                SerialResponseCollector::Fs => {
                                    return Ok(Some(SerialProtocolResult::Fs(fs_result)));
                                }
                                SerialResponseCollector::Http => {
                                    return Err(MicrovmError::Backend(
                                        "unexpected FSRESULT frame while waiting for HTTP response"
                                            .into(),
                                    ));
                                }
                            }
                        }
                    }
                    SerialFrame::Stream(stream_result) => {
                        if let Some(response) = response.as_deref_mut() {
                            match response {
                                SerialResponseCollector::Command(_) => {
                                    return Ok(Some(stream_result));
                                }
                                SerialResponseCollector::Fs => {
                                    return Err(MicrovmError::Backend(format!(
                                        "unexpected STREAM frame while waiting for FSRESULT: {stream_result:?}"
                                    )));
                                }
                                SerialResponseCollector::Http => {
                                    return Ok(Some(stream_result));
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    Ok(None)
}

#[cfg(not(any(debug_assertions, feature = "boot-profile")))]
pub(super) fn handle_serial_write(
    serial_device: &mut SerialDevice,
    serial_buffer: &mut Vec<u8>,
    guest_ready: &mut bool,
    port: u16,
    data: &[u8],
    frame_buffer: &mut Vec<u8>,
    response: Option<&mut SerialResponseCollector>,
) -> Result<Option<SerialProtocolResult>, MicrovmError> {
    let mut response = response;

    for &value in data {
        if let Some(tx_byte) = serial_device.write(port, value)? {
            serial_buffer.push(tx_byte);
            if tx_byte != b'\r' {
                frame_buffer.push(tx_byte);
            }

            while let Some(frame) = take_serial_frame(frame_buffer)? {
                match frame {
                    SerialFrame::Line(line) => {
                        if line == SERIAL_READY_LINE {
                            *guest_ready = true;
                            continue;
                        }
                        if line == SERIAL_PONG_LINE {
                            return Ok(Some(SerialProtocolResult::PingPong));
                        }

                        if let Some(response) = response.as_deref_mut() {
                            match response {
                                SerialResponseCollector::Command(command_response) => {
                                    if let Some(result) =
                                        parse_serial_line(&line, command_response)?
                                    {
                                        return Ok(Some(SerialProtocolResult::Command(result)));
                                    }
                                }
                                SerialResponseCollector::Fs => {
                                    if line.starts_with("OUTPUT:") || line.starts_with("EXIT:") {
                                        return Err(MicrovmError::Backend(format!(
                                            "unexpected serial line while waiting for FSRESULT: {line}"
                                        )));
                                    }
                                }
                                SerialResponseCollector::Http => {
                                    if line.starts_with("OUTPUT:") || line.starts_with("EXIT:") {
                                        return Err(MicrovmError::Backend(format!(
                                            "unexpected serial line while waiting for HTTP response: {line}"
                                        )));
                                    }
                                }
                            }
                        }
                    }
                    SerialFrame::FsResult(fs_result) => {
                        if let Some(response) = response.as_deref_mut() {
                            match response {
                                SerialResponseCollector::Command(_) => {
                                    return Err(MicrovmError::Backend(
                                        "unexpected FSRESULT frame during command execution".into(),
                                    ));
                                }
                                SerialResponseCollector::Fs => {
                                    return Ok(Some(SerialProtocolResult::Fs(fs_result)));
                                }
                                SerialResponseCollector::Http => {
                                    return Err(MicrovmError::Backend(
                                        "unexpected FSRESULT frame while waiting for HTTP response"
                                            .into(),
                                    ));
                                }
                            }
                        }
                    }
                    SerialFrame::Stream(stream_result) => {
                        if let Some(response) = response.as_deref_mut() {
                            match response {
                                SerialResponseCollector::Command(_) => {
                                    return Ok(Some(stream_result));
                                }
                                SerialResponseCollector::Fs => {
                                    return Err(MicrovmError::Backend(format!(
                                        "unexpected STREAM frame while waiting for FSRESULT: {stream_result:?}"
                                    )));
                                }
                                SerialResponseCollector::Http => {
                                    return Ok(Some(stream_result));
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    Ok(None)
}

pub(super) fn handle_serial_read(
    serial_device: &mut SerialDevice,
    port: u16,
    data: &mut [u8],
) -> Result<(), MicrovmError> {
    for slot in data.iter_mut() {
        *slot = serial_device.read(port)?;
    }
    Ok(())
}