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
7 changes: 7 additions & 0 deletions .github/workflows/qemu.yml
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ jobs:
--enable-kvm \
--enable-slirp \
--enable-strip \
--enable-vhost-kernel \
--static \
--disable-docs \
--disable-user \
Expand All @@ -117,6 +118,12 @@ jobs:
- name: Install TPM 2.0 Reference Implementation build dependencies
run: sudo apt install -y build-essential cmake pkg-config

- name: Install netcat used by test-in-svsm for vsock tests
run: sudo apt install -y ncat

- name: Set up vhost-vsock permissions
run: sudo chmod 666 /dev/vhost-vsock

- name: Build test
run: make bin/coconut-test-qemu.igvm

Expand Down
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ SVSM_ARGS += --features ${FEATURES}
XBUILD_ARGS += -f ${FEATURES}
endif

FEATURES_TEST ?= vtpm,virtio-drivers,block
FEATURES_TEST ?= vtpm,virtio-drivers,block,vsock
SVSM_ARGS_TEST += --no-default-features
ifneq ($(FEATURES_TEST),)
SVSM_ARGS_TEST += --features ${FEATURES_TEST}
Expand Down
1 change: 1 addition & 0 deletions kernel/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ verus = ["verus_all", "verify_proof/noverify", "verify_external/noverify"]
noverify = []
virtio-drivers = ["dep:virtio-drivers"]
block = []
vsock = []

[dev-dependencies]
sha2 = { workspace = true, features = ["force-soft"] }
Expand Down
12 changes: 12 additions & 0 deletions kernel/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ use crate::tdx::TdxError;
use crate::utils::immut_after_init::ImmutAfterInitError;
#[cfg(feature = "virtio-drivers")]
use crate::virtio::VirtioError;
#[cfg(feature = "vsock")]
use crate::vsock::VsockError;
use elf::ElfError;
use syscall::SysCallError;

Expand Down Expand Up @@ -143,6 +145,9 @@ pub enum SvsmError {
TeeAttestation(AttestationError),
/// Errors related to ImmutAfterInitCell
ImmutAfterInit(ImmutAfterInitError),
/// Errors related to vsock.
#[cfg(feature = "vsock")]
Vsock(VsockError),
}

impl From<ElfError> for SvsmError {
Expand Down Expand Up @@ -183,6 +188,13 @@ impl From<BlockDeviceError> for SvsmError {
}
}

#[cfg(feature = "vsock")]
impl From<VsockError> for SvsmError {
fn from(err: VsockError) -> Self {
Self::Vsock(err)
}
}

impl From<SvsmError> for SysCallError {
fn from(err: SvsmError) -> Self {
match err {
Expand Down
2 changes: 2 additions & 0 deletions kernel/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ pub mod utils;
#[cfg(feature = "virtio-drivers")]
pub mod virtio;
pub mod vmm;
#[cfg(feature = "vsock")]
pub mod vsock;
#[cfg(all(feature = "vtpm", not(test)))]
pub mod vtpm;

Expand Down
13 changes: 10 additions & 3 deletions kernel/src/svsm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ use svsm::svsm_paging::invalidate_early_boot_memory;
use svsm::task::{KernelThreadStartInfo, schedule_init, start_kernel_task};
use svsm::types::PAGE_SIZE;
use svsm::utils::{MemoryRegion, ScopedRef, round_to_pages};
#[cfg(all(feature = "virtio-drivers", feature = "block"))]
#[cfg(all(feature = "virtio-drivers", any(feature = "block", feature = "vsock")))]
use svsm::virtio::probe_mmio_slots;
#[cfg(all(feature = "vtpm", not(test)))]
use svsm::vtpm::vtpm_init;
Expand Down Expand Up @@ -217,14 +217,21 @@ fn mapping_info_init(launch_info: &KernelLaunchInfo) {
/// Returns an error when a virtio device is found but its driver initialization fails.
#[cfg(feature = "virtio-drivers")]
fn initialize_virtio_mmio(_boot_params: &BootParams<'_>) -> Result<(), SvsmError> {
#[cfg(any(feature = "block", feature = "vsock"))]
let mut slots = probe_mmio_slots(_boot_params);

#[cfg(feature = "block")]
{
use svsm::block::virtio_blk::initialize_block;

let mut slots = probe_mmio_slots(_boot_params);
initialize_block(&mut slots)?;
}

#[cfg(feature = "vsock")]
{
use svsm::vsock::virtio_vsock::initialize_vsock;
initialize_vsock(&mut slots)?;
}

Ok(())
}

Expand Down
2 changes: 2 additions & 0 deletions kernel/src/testing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ pub enum IORequest {
GetLaunchMeasurement = 0x01,
/// Virtio-blk tests: Get Sha256 hash of the svsm state disk image
GetStateImageSha256 = 0x02,
/// Virtio-vsock tests: Ask host to start a vsock server
StartVsockServer = 0x03,
}

/// Return the serial port to communicate with the host for a given request
Expand Down
110 changes: 110 additions & 0 deletions kernel/src/vsock/api.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
// SPDX-License-Identifier: MIT
//
// Copyright (c) 2025 Red Hat, Inc.
//
// Author: Luigi Leonardi <leonardi@redhat.com>

use crate::error::SvsmError;

pub trait VsockTransport: Sync + Send {
/// Establishes a connection to a remote vsock endpoint.
///
/// This method initiates a connection to the specified remote CID and port
/// using the provided local port. The call blocks until the connection is
/// established or fails.
///
/// # Parameters
///
/// * `remote_cid` - The CID of the remote endpoint to connect to
/// * `local_port` - The local port to use for this connection
/// * `remote_port` - The remote port to connect to
///
/// # Returns
///
/// * `Ok()` if the connection was successfully established
/// * `Err(SvsmError)` if the connection failed
fn connect(&self, remote_cid: u32, local_port: u32, remote_port: u32) -> Result<(), SvsmError>;

/// Sends data over an established vsock connection.
///
/// Transmits the contents of the provided buffer to the remote endpoint.
/// The connection must have been previously established via `connect()`.
///
/// # Parameters
///
/// * `remote_cid` - The CID of the remote endpoint
/// * `local_port` - The local port of the connection
/// * `remote_port` - The remote port of the connection
/// * `buffer` - The data to send
///
/// # Returns
///
/// * `Ok(usize)` - The number of bytes successfully sent
/// * `Err(SvsmError)` if the send operation failed
fn send(
&self,
remote_cid: u32,
local_port: u32,
remote_port: u32,
buffer: &[u8],
) -> Result<usize, SvsmError>;

/// Receives data from an established vsock connection.
///
/// Reads data from the remote endpoint into the provided buffer. This method
/// blocks until all data is available or an error occurs, in such case
/// returns all the received bytes, if any.
/// The connection must have been previously established via `connect()`.
///
/// # Parameters
///
/// * `remote_cid` - The CID of the remote endpoint
/// * `local_port` - The local port of the connection
/// * `remote_port` - The remote port of the connection
/// * `buffer` - The buffer to receive data into
///
/// # Returns
///
/// * `Ok(usize)` - The number of bytes successfully received
/// * `Err(SvsmError)` if the receive operation failed
fn recv(
&self,
remote_cid: u32,
local_port: u32,
remote_port: u32,
buffer: &mut [u8],
) -> Result<usize, SvsmError>;

/// Shuts down a vsock connection.
///
/// Initiates a graceful shutdown of the connection telling the peer that we won't
/// send or receive any more data.
///
/// # Parameters
///
/// * `remote_cid` - The CID of the remote endpoint
/// * `local_port` - The local port of the connection
/// * `remote_port` - The remote port of the connection
/// * `force` - Forcibly terminates the connection, without waiting for peer confirm
///
/// # Returns
///
/// * `Ok()` if the shutdown was successful
/// * `Err(SvsmError)` if the shutdown failed
fn shutdown(
&self,
remote_cid: u32,
local_port: u32,
remote_port: u32,
force: bool,
) -> Result<(), SvsmError>;

/// Returns whether the given local port is currently in use.
///
/// # Returns
///
/// * `Ok(true)` - The port is in use
/// * `Ok(false)` - The port is free
/// * `Err(SvsmError)` if the check could not be performed
fn is_local_port_used(&self, port: u32) -> Result<bool, SvsmError>;
}
21 changes: 21 additions & 0 deletions kernel/src/vsock/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// SPDX-License-Identifier: MIT
//
// Copyright (c) 2025 Red Hat, Inc.
//
// Author: Luigi Leonardi <leonardi@redhat.com>

#[derive(Debug, Clone, Copy, PartialEq)]
pub enum VsockError {
/// A connection already exists.
ConnectionExists,
/// The device is not connected to any peer.
NotConnected,
/// The peer socket has shutdown.
PeerSocketShutdown,
/// The local socket has been shutdown.
SocketShutdown,
/// No local ports are available.
NoPortsAvailable,
/// Generic error for socket operations on a vsock device.
DriverError,
}
73 changes: 73 additions & 0 deletions kernel/src/vsock/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// SPDX-License-Identifier: MIT
//
// Copyright (c) 2025 Red Hat, Inc.
//
// Author: Luigi Leonardi <leonardi@redhat.com>

pub mod api;
pub mod error;
pub mod stream;
#[cfg(feature = "virtio-drivers")]
pub mod virtio_vsock;

pub use error::VsockError;
/// Well-known CID for the host.
pub const VMADDR_CID_HOST: u32 = 2;
pub const VMADDR_PORT_ANY: u32 = u32::MAX;

extern crate alloc;
use crate::{
error::SvsmError, utils::immut_after_init::ImmutAfterInitCell, vsock::api::VsockTransport,
};
use alloc::boxed::Box;
use core::ops::Deref;
use core::sync::atomic::{AtomicU32, Ordering};

// Currently only one vsock device is supported.
static VSOCK_DEVICE: ImmutAfterInitCell<VsockDriver> = ImmutAfterInitCell::uninit();
// Ports below 1024 are reserved
const VSOCK_MIN_PORT: u32 = 1024;
// Number of maximum retries to get a local free port
const MAX_RETRIES: u32 = 5;

struct VsockDriver {
first_free_port: AtomicU32,
transport: Box<dyn VsockTransport>,
}

impl VsockDriver {
/// Returns a free local port number for a new connection.
///
/// Returns [`VsockError::NoPortsAvailable`] if all
/// ports are already in use.
fn get_first_free_port(&self) -> Result<u32, SvsmError> {
for _ in 0..MAX_RETRIES {
let candidate_port =
self.first_free_port
.fetch_update(Ordering::Relaxed, Ordering::Relaxed, |port| {
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure about this, I'd like some feedback from someone with more experience

if port >= VMADDR_PORT_ANY - 1 {
Some(VSOCK_MIN_PORT)
} else {
Some(port + 1)
}
});

// The closure always returns Some, so this never fails.
let candidate_port = candidate_port.unwrap();

if !self.is_local_port_used(candidate_port)? {
return Ok(candidate_port);
}
}

Err(SvsmError::Vsock(VsockError::NoPortsAvailable))
}
}

impl Deref for VsockDriver {
type Target = dyn VsockTransport;

fn deref(&self) -> &Self::Target {
self.transport.as_ref()
}
}
Loading
Loading