vmm: use trait objects for API actions

Uses of the old ApiRequest enum conflated two different concerns:
identifying an API request endpoint, and storing data for an API
request.  This led to ApiRequest values being passed around with junk
data just to communicate a request type, which forced all API request
body types to implement Default, which in some cases doesn't make any
sense — what's the "default" path for a vhost-user socket?  The
nonsensical Default values have led to tests relying on being able to
use nonsensical data, which is an impediment to adding better
validation for these types.

Rather than having API request types be represented by an enum, which
has to carry associated body data everywhere it's used, it makes more
sense to represent API request types as trait objects.  These can have
an associated type for the type of the request body, and this makes it
possible to pass API request types and data around as siblings in a
type-safe way without forcing them into a single value even where it
doesn't make sense.  Trait objects also give us dynamic dispatch,
which lets us get rid of several large match blocks.

To keep it possible to fuzz the HTTP API, all the Vmm methods called
by the HTTP API are pulled out into a trait, so the fuzzer can provide
its own stub implementation of the VMM.

Signed-off-by: Alyssa Ross <hi@alyssa.is>
This commit is contained in:
Alyssa Ross
2024-01-05 15:08:53 +01:00
committed by Rob Bradford
parent 6aa7afbb6f
commit 4ca18c082e
9 changed files with 2041 additions and 1409 deletions

View File

@@ -1,17 +1,17 @@
// Copyright © 2019 Intel Corporation
// Copyright 2024 Alyssa Ross <hi@alyssa.is>
//
// SPDX-License-Identifier: Apache-2.0
//
use crate::api::http::{error_response, EndpointHandler, HttpError};
#[cfg(all(target_arch = "x86_64", feature = "guest_debug"))]
use crate::api::vm_coredump;
use crate::api::VmCoredump;
use crate::api::{
vm_add_device, vm_add_disk, vm_add_fs, vm_add_net, vm_add_pmem, vm_add_user_device,
vm_add_vdpa, vm_add_vsock, vm_boot, vm_counters, vm_create, vm_delete, vm_info, vm_pause,
vm_power_button, vm_reboot, vm_receive_migration, vm_remove_device, vm_resize, vm_resize_zone,
vm_restore, vm_resume, vm_send_migration, vm_shutdown, vm_snapshot, vmm_ping, vmm_shutdown,
ApiRequest, VmAction, VmConfig,
AddDisk, ApiAction, ApiRequest, VmAddDevice, VmAddFs, VmAddNet, VmAddPmem, VmAddUserDevice,
VmAddVdpa, VmAddVsock, VmBoot, VmConfig, VmCounters, VmDelete, VmPause, VmPowerButton,
VmReboot, VmReceiveMigration, VmRemoveDevice, VmResize, VmResizeZone, VmRestore, VmResume,
VmSendMigration, VmShutdown, VmSnapshot,
};
use crate::config::NetConfig;
use micro_http::{Body, Method, Request, Response, StatusCode, Version};
@@ -52,8 +52,8 @@ impl EndpointHandler for VmCreate {
}
}
// Call vm_create()
match vm_create(api_notifier, api_sender, Arc::new(Mutex::new(vm_config)))
match crate::api::VmCreate
.send(api_notifier, api_sender, Arc::new(Mutex::new(vm_config)))
.map_err(HttpError::ApiError)
{
Ok(_) => Response::new(Version::Http11, StatusCode::NoContent),
@@ -70,13 +70,162 @@ impl EndpointHandler for VmCreate {
}
}
pub trait GetHandler {
fn handle_request(
&'static self,
_api_notifier: EventFd,
_api_sender: Sender<ApiRequest>,
) -> std::result::Result<Option<Body>, HttpError> {
Err(HttpError::BadRequest)
}
}
pub trait PutHandler {
fn handle_request(
&'static self,
_api_notifier: EventFd,
_api_sender: Sender<ApiRequest>,
_body: &Option<Body>,
_files: Vec<File>,
) -> std::result::Result<Option<Body>, HttpError> {
Err(HttpError::BadRequest)
}
}
pub trait HttpVmAction: GetHandler + PutHandler + Sync {}
impl<T: GetHandler + PutHandler + Sync> HttpVmAction for T {}
macro_rules! vm_action_get_handler {
($action:ty) => {
impl GetHandler for $action {
fn handle_request(
&'static self,
api_notifier: EventFd,
api_sender: Sender<ApiRequest>,
) -> std::result::Result<Option<Body>, HttpError> {
self.send(api_notifier, api_sender, ())
.map_err(HttpError::ApiError)
}
}
impl PutHandler for $action {}
};
}
macro_rules! vm_action_put_handler {
($action:ty) => {
impl PutHandler for $action {
fn handle_request(
&'static self,
api_notifier: EventFd,
api_sender: Sender<ApiRequest>,
body: &Option<Body>,
_files: Vec<File>,
) -> std::result::Result<Option<Body>, HttpError> {
if body.is_some() {
Err(HttpError::BadRequest)
} else {
self.send(api_notifier, api_sender, ())
.map_err(HttpError::ApiError)
}
}
}
impl GetHandler for $action {}
};
}
macro_rules! vm_action_put_handler_body {
($action:ty) => {
impl PutHandler for $action {
fn handle_request(
&'static self,
api_notifier: EventFd,
api_sender: Sender<ApiRequest>,
body: &Option<Body>,
_files: Vec<File>,
) -> std::result::Result<Option<Body>, HttpError> {
if let Some(body) = body {
self.send(
api_notifier,
api_sender,
serde_json::from_slice(body.raw())?,
)
.map_err(HttpError::ApiError)
} else {
Err(HttpError::BadRequest)
}
}
}
impl GetHandler for $action {}
};
}
vm_action_get_handler!(VmCounters);
vm_action_put_handler!(VmBoot);
vm_action_put_handler!(VmDelete);
vm_action_put_handler!(VmShutdown);
vm_action_put_handler!(VmReboot);
vm_action_put_handler!(VmPause);
vm_action_put_handler!(VmResume);
vm_action_put_handler!(VmPowerButton);
vm_action_put_handler_body!(VmAddDevice);
vm_action_put_handler_body!(AddDisk);
vm_action_put_handler_body!(VmAddFs);
vm_action_put_handler_body!(VmAddPmem);
vm_action_put_handler_body!(VmAddVdpa);
vm_action_put_handler_body!(VmAddVsock);
vm_action_put_handler_body!(VmAddUserDevice);
vm_action_put_handler_body!(VmRemoveDevice);
vm_action_put_handler_body!(VmResize);
vm_action_put_handler_body!(VmResizeZone);
vm_action_put_handler_body!(VmRestore);
vm_action_put_handler_body!(VmSnapshot);
vm_action_put_handler_body!(VmReceiveMigration);
vm_action_put_handler_body!(VmSendMigration);
#[cfg(all(target_arch = "x86_64", feature = "guest_debug"))]
vm_action_put_handler_body!(VmCoredump);
impl PutHandler for VmAddNet {
fn handle_request(
&'static self,
api_notifier: EventFd,
api_sender: Sender<ApiRequest>,
body: &Option<Body>,
mut files: Vec<File>,
) -> std::result::Result<Option<Body>, HttpError> {
if let Some(body) = body {
let mut net_cfg: NetConfig = serde_json::from_slice(body.raw())?;
if net_cfg.fds.is_some() {
warn!("Ignoring FDs sent via the HTTP request body");
net_cfg.fds = None;
}
if !files.is_empty() {
let fds = files.drain(..).map(|f| f.into_raw_fd()).collect();
net_cfg.fds = Some(fds);
}
self.send(api_notifier, api_sender, net_cfg)
.map_err(HttpError::ApiError)
} else {
Err(HttpError::BadRequest)
}
}
}
impl GetHandler for VmAddNet {}
// Common handler for boot, shutdown and reboot
pub struct VmActionHandler {
action: VmAction,
action: &'static dyn HttpVmAction,
}
impl VmActionHandler {
pub fn new(action: VmAction) -> Self {
pub fn new(action: &'static dyn HttpVmAction) -> Self {
VmActionHandler { action }
}
}
@@ -87,117 +236,9 @@ impl EndpointHandler for VmActionHandler {
api_notifier: EventFd,
api_sender: Sender<ApiRequest>,
body: &Option<Body>,
mut files: Vec<File>,
files: Vec<File>,
) -> std::result::Result<Option<Body>, HttpError> {
use VmAction::*;
if let Some(body) = body {
match self.action {
AddDevice(_) => vm_add_device(
api_notifier,
api_sender,
Arc::new(serde_json::from_slice(body.raw())?),
),
AddDisk(_) => vm_add_disk(
api_notifier,
api_sender,
Arc::new(serde_json::from_slice(body.raw())?),
),
AddFs(_) => vm_add_fs(
api_notifier,
api_sender,
Arc::new(serde_json::from_slice(body.raw())?),
),
AddPmem(_) => vm_add_pmem(
api_notifier,
api_sender,
Arc::new(serde_json::from_slice(body.raw())?),
),
AddNet(_) => {
let mut net_cfg: NetConfig = serde_json::from_slice(body.raw())?;
if net_cfg.fds.is_some() {
warn!("Ignoring FDs sent via the HTTP request body");
net_cfg.fds = None;
}
// Update network config with optional files that might have
// been sent through control message.
if !files.is_empty() {
let fds = files.drain(..).map(|f| f.into_raw_fd()).collect();
net_cfg.fds = Some(fds);
}
vm_add_net(api_notifier, api_sender, Arc::new(net_cfg))
}
AddVdpa(_) => vm_add_vdpa(
api_notifier,
api_sender,
Arc::new(serde_json::from_slice(body.raw())?),
),
AddVsock(_) => vm_add_vsock(
api_notifier,
api_sender,
Arc::new(serde_json::from_slice(body.raw())?),
),
AddUserDevice(_) => vm_add_user_device(
api_notifier,
api_sender,
Arc::new(serde_json::from_slice(body.raw())?),
),
RemoveDevice(_) => vm_remove_device(
api_notifier,
api_sender,
Arc::new(serde_json::from_slice(body.raw())?),
),
Resize(_) => vm_resize(
api_notifier,
api_sender,
Arc::new(serde_json::from_slice(body.raw())?),
),
ResizeZone(_) => vm_resize_zone(
api_notifier,
api_sender,
Arc::new(serde_json::from_slice(body.raw())?),
),
Restore(_) => vm_restore(
api_notifier,
api_sender,
Arc::new(serde_json::from_slice(body.raw())?),
),
Snapshot(_) => vm_snapshot(
api_notifier,
api_sender,
Arc::new(serde_json::from_slice(body.raw())?),
),
#[cfg(all(target_arch = "x86_64", feature = "guest_debug"))]
Coredump(_) => vm_coredump(
api_notifier,
api_sender,
Arc::new(serde_json::from_slice(body.raw())?),
),
ReceiveMigration(_) => vm_receive_migration(
api_notifier,
api_sender,
Arc::new(serde_json::from_slice(body.raw())?),
),
SendMigration(_) => vm_send_migration(
api_notifier,
api_sender,
Arc::new(serde_json::from_slice(body.raw())?),
),
_ => return Err(HttpError::BadRequest),
}
} else {
match self.action {
Boot => vm_boot(api_notifier, api_sender),
Delete => vm_delete(api_notifier, api_sender),
Shutdown => vm_shutdown(api_notifier, api_sender),
Reboot => vm_reboot(api_notifier, api_sender),
Pause => vm_pause(api_notifier, api_sender),
Resume => vm_resume(api_notifier, api_sender),
PowerButton => vm_power_button(api_notifier, api_sender),
_ => return Err(HttpError::BadRequest),
}
}
.map_err(HttpError::ApiError)
PutHandler::handle_request(self.action, api_notifier, api_sender, body, files)
}
fn get_handler(
@@ -206,11 +247,7 @@ impl EndpointHandler for VmActionHandler {
api_sender: Sender<ApiRequest>,
_body: &Option<Body>,
) -> std::result::Result<Option<Body>, HttpError> {
use VmAction::*;
match self.action {
Counters => vm_counters(api_notifier, api_sender).map_err(HttpError::ApiError),
_ => Err(HttpError::BadRequest),
}
GetHandler::handle_request(self.action, api_notifier, api_sender)
}
}
@@ -225,7 +262,10 @@ impl EndpointHandler for VmInfo {
api_sender: Sender<ApiRequest>,
) -> Response {
match req.method() {
Method::Get => match vm_info(api_notifier, api_sender).map_err(HttpError::ApiError) {
Method::Get => match crate::api::VmInfo
.send(api_notifier, api_sender, ())
.map_err(HttpError::ApiError)
{
Ok(info) => {
let mut response = Response::new(Version::Http11, StatusCode::OK);
let info_serialized = serde_json::to_string(&info).unwrap();
@@ -251,7 +291,10 @@ impl EndpointHandler for VmmPing {
api_sender: Sender<ApiRequest>,
) -> Response {
match req.method() {
Method::Get => match vmm_ping(api_notifier, api_sender).map_err(HttpError::ApiError) {
Method::Get => match crate::api::VmmPing
.send(api_notifier, api_sender, ())
.map_err(HttpError::ApiError)
{
Ok(pong) => {
let mut response = Response::new(Version::Http11, StatusCode::OK);
let info_serialized = serde_json::to_string(&pong).unwrap();
@@ -279,7 +322,10 @@ impl EndpointHandler for VmmShutdown {
) -> Response {
match req.method() {
Method::Put => {
match vmm_shutdown(api_notifier, api_sender).map_err(HttpError::ApiError) {
match crate::api::VmmShutdown
.send(api_notifier, api_sender, ())
.map_err(HttpError::ApiError)
{
Ok(_) => Response::new(Version::Http11, StatusCode::OK),
Err(e) => error_response(e, StatusCode::InternalServerError),
}

View File

@@ -4,7 +4,14 @@
//
use self::http_endpoint::{VmActionHandler, VmCreate, VmInfo, VmmPing, VmmShutdown};
use crate::api::{ApiError, ApiRequest, VmAction};
#[cfg(all(target_arch = "x86_64", feature = "guest_debug"))]
use crate::api::VmCoredump;
use crate::api::{
AddDisk, ApiError, ApiRequest, VmAddDevice, VmAddFs, VmAddNet, VmAddPmem, VmAddUserDevice,
VmAddVdpa, VmAddVsock, VmBoot, VmCounters, VmDelete, VmPause, VmPowerButton, VmReboot,
VmReceiveMigration, VmRemoveDevice, VmResize, VmResizeZone, VmRestore, VmResume,
VmSendMigration, VmShutdown, VmSnapshot,
};
use crate::seccomp_filters::{get_seccomp_filter, Thread};
use crate::{Error as VmmError, Result};
use hypervisor::HypervisorType;
@@ -19,7 +26,6 @@ use std::os::unix::net::UnixListener;
use std::panic::AssertUnwindSafe;
use std::path::PathBuf;
use std::sync::mpsc::Sender;
use std::sync::Arc;
use std::thread;
use vmm_sys_util::eventfd::EventFd;
@@ -141,109 +147,103 @@ pub static HTTP_ROUTES: Lazy<HttpRoutes> = Lazy::new(|| {
r.routes.insert(
endpoint!("/vm.add-device"),
Box::new(VmActionHandler::new(VmAction::AddDevice(Arc::default()))),
Box::new(VmActionHandler::new(&VmAddDevice)),
);
r.routes.insert(
endpoint!("/vm.add-user-device"),
Box::new(VmActionHandler::new(
VmAction::AddUserDevice(Arc::default()),
)),
Box::new(VmActionHandler::new(&VmAddUserDevice)),
);
r.routes.insert(
endpoint!("/vm.add-disk"),
Box::new(VmActionHandler::new(VmAction::AddDisk(Arc::default()))),
Box::new(VmActionHandler::new(&AddDisk)),
);
r.routes.insert(
endpoint!("/vm.add-fs"),
Box::new(VmActionHandler::new(VmAction::AddFs(Arc::default()))),
Box::new(VmActionHandler::new(&VmAddFs)),
);
r.routes.insert(
endpoint!("/vm.add-net"),
Box::new(VmActionHandler::new(VmAction::AddNet(Arc::default()))),
Box::new(VmActionHandler::new(&VmAddNet)),
);
r.routes.insert(
endpoint!("/vm.add-pmem"),
Box::new(VmActionHandler::new(VmAction::AddPmem(Arc::default()))),
Box::new(VmActionHandler::new(&VmAddPmem)),
);
r.routes.insert(
endpoint!("/vm.add-vdpa"),
Box::new(VmActionHandler::new(VmAction::AddVdpa(Arc::default()))),
Box::new(VmActionHandler::new(&VmAddVdpa)),
);
r.routes.insert(
endpoint!("/vm.add-vsock"),
Box::new(VmActionHandler::new(VmAction::AddVsock(Arc::default()))),
Box::new(VmActionHandler::new(&VmAddVsock)),
);
r.routes.insert(
endpoint!("/vm.boot"),
Box::new(VmActionHandler::new(VmAction::Boot)),
Box::new(VmActionHandler::new(&VmBoot)),
);
r.routes.insert(
endpoint!("/vm.counters"),
Box::new(VmActionHandler::new(VmAction::Counters)),
Box::new(VmActionHandler::new(&VmCounters)),
);
r.routes
.insert(endpoint!("/vm.create"), Box::new(VmCreate {}));
r.routes.insert(
endpoint!("/vm.delete"),
Box::new(VmActionHandler::new(VmAction::Delete)),
Box::new(VmActionHandler::new(&VmDelete)),
);
r.routes.insert(endpoint!("/vm.info"), Box::new(VmInfo {}));
r.routes.insert(
endpoint!("/vm.pause"),
Box::new(VmActionHandler::new(VmAction::Pause)),
Box::new(VmActionHandler::new(&VmPause)),
);
r.routes.insert(
endpoint!("/vm.power-button"),
Box::new(VmActionHandler::new(VmAction::PowerButton)),
Box::new(VmActionHandler::new(&VmPowerButton)),
);
r.routes.insert(
endpoint!("/vm.reboot"),
Box::new(VmActionHandler::new(VmAction::Reboot)),
Box::new(VmActionHandler::new(&VmReboot)),
);
r.routes.insert(
endpoint!("/vm.receive-migration"),
Box::new(VmActionHandler::new(VmAction::ReceiveMigration(
Arc::default(),
))),
Box::new(VmActionHandler::new(&VmReceiveMigration)),
);
r.routes.insert(
endpoint!("/vm.remove-device"),
Box::new(VmActionHandler::new(VmAction::RemoveDevice(Arc::default()))),
Box::new(VmActionHandler::new(&VmRemoveDevice)),
);
r.routes.insert(
endpoint!("/vm.resize"),
Box::new(VmActionHandler::new(VmAction::Resize(Arc::default()))),
Box::new(VmActionHandler::new(&VmResize)),
);
r.routes.insert(
endpoint!("/vm.resize-zone"),
Box::new(VmActionHandler::new(VmAction::ResizeZone(Arc::default()))),
Box::new(VmActionHandler::new(&VmResizeZone)),
);
r.routes.insert(
endpoint!("/vm.restore"),
Box::new(VmActionHandler::new(VmAction::Restore(Arc::default()))),
Box::new(VmActionHandler::new(&VmRestore)),
);
r.routes.insert(
endpoint!("/vm.resume"),
Box::new(VmActionHandler::new(VmAction::Resume)),
Box::new(VmActionHandler::new(&VmResume)),
);
r.routes.insert(
endpoint!("/vm.send-migration"),
Box::new(VmActionHandler::new(
VmAction::SendMigration(Arc::default()),
)),
Box::new(VmActionHandler::new(&VmSendMigration)),
);
r.routes.insert(
endpoint!("/vm.shutdown"),
Box::new(VmActionHandler::new(VmAction::Shutdown)),
Box::new(VmActionHandler::new(&VmShutdown)),
);
r.routes.insert(
endpoint!("/vm.snapshot"),
Box::new(VmActionHandler::new(VmAction::Snapshot(Arc::default()))),
Box::new(VmActionHandler::new(&VmSnapshot)),
);
#[cfg(all(target_arch = "x86_64", feature = "guest_debug"))]
r.routes.insert(
endpoint!("/vm.coredump"),
Box::new(VmActionHandler::new(VmAction::Coredump(Arc::default()))),
Box::new(VmActionHandler::new(&VmCoredump)),
);
r.routes
.insert(endpoint!("/vmm.ping"), Box::new(VmmPing {}));