diff --git a/vhost/Cargo.toml b/vhost/Cargo.toml index 44388bc..b37ffd8 100644 --- a/vhost/Cargo.toml +++ b/vhost/Cargo.toml @@ -29,6 +29,7 @@ postcopy = [] [dependencies] bitflags = "2.4" libc = "0.2.39" +uuid = { version = "1.8.0", features=["v4", "fast-rng", "macro-diagnostics"] } vmm-sys-util = "0.12.1" vm-memory = { version = "0.14.0", features=["backend-mmap"] } diff --git a/vhost/src/vhost_user/backend_req.rs b/vhost/src/vhost_user/backend_req.rs index b43982f..ecb6a72 100644 --- a/vhost/src/vhost_user/backend_req.rs +++ b/vhost/src/vhost_user/backend_req.rs @@ -129,6 +129,29 @@ impl Backend { } impl VhostUserFrontendReqHandler for Backend { + /// Forward vhost-user shared-object add request to the frontend. + fn shared_object_add(&self, uuid: &VhostUserSharedMsg) -> HandlerResult { + self.send_message(BackendReq::SHARED_OBJECT_ADD, uuid, None) + } + + /// Forward vhost-user shared-object remove request to the frontend. + fn shared_object_remove(&self, uuid: &VhostUserSharedMsg) -> HandlerResult { + self.send_message(BackendReq::SHARED_OBJECT_REMOVE, uuid, None) + } + + /// Forward vhost-user shared-object lookup request to the frontend. + fn shared_object_lookup( + &self, + uuid: &VhostUserSharedMsg, + fd: &dyn AsRawFd, + ) -> HandlerResult { + self.send_message( + BackendReq::SHARED_OBJECT_LOOKUP, + uuid, + Some(&[fd.as_raw_fd()]), + ) + } + /// Forward vhost-user-fs map file requests to the backend. fn fs_backend_map(&self, fs: &VhostUserFSBackendMsg, fd: &dyn AsRawFd) -> HandlerResult { self.send_message(BackendReq::FS_MAP, fs, Some(&[fd.as_raw_fd()])) diff --git a/vhost/src/vhost_user/frontend_req_handler.rs b/vhost/src/vhost_user/frontend_req_handler.rs index 3a2f457..196da2d 100644 --- a/vhost/src/vhost_user/frontend_req_handler.rs +++ b/vhost/src/vhost_user/frontend_req_handler.rs @@ -33,6 +33,25 @@ pub trait VhostUserFrontendReqHandler { Err(std::io::Error::from_raw_os_error(libc::ENOSYS)) } + /// Handle shared object add operation + fn shared_object_add(&self, _uuid: &VhostUserSharedMsg) -> HandlerResult { + Err(std::io::Error::from_raw_os_error(libc::ENOSYS)) + } + + /// Handle shared object remove operation + fn shared_object_remove(&self, _uuid: &VhostUserSharedMsg) -> HandlerResult { + Err(std::io::Error::from_raw_os_error(libc::ENOSYS)) + } + + /// Handle shared object lookup operation + fn shared_object_lookup( + &self, + _uuid: &VhostUserSharedMsg, + _fd: &dyn AsRawFd, + ) -> HandlerResult { + Err(std::io::Error::from_raw_os_error(libc::ENOSYS)) + } + /// Handle virtio-fs map file requests. fn fs_backend_map(&self, _fs: &VhostUserFSBackendMsg, _fd: &dyn AsRawFd) -> HandlerResult { Err(std::io::Error::from_raw_os_error(libc::ENOSYS)) @@ -66,6 +85,25 @@ pub trait VhostUserFrontendReqHandlerMut { Err(std::io::Error::from_raw_os_error(libc::ENOSYS)) } + /// Handle shared object add operation + fn shared_object_add(&mut self, _uuid: &VhostUserSharedMsg) -> HandlerResult { + Err(std::io::Error::from_raw_os_error(libc::ENOSYS)) + } + + /// Handle shared object remove operation + fn shared_object_remove(&mut self, _uuid: &VhostUserSharedMsg) -> HandlerResult { + Err(std::io::Error::from_raw_os_error(libc::ENOSYS)) + } + + /// Handle shared object lookup operation + fn shared_object_lookup( + &mut self, + _uuid: &VhostUserSharedMsg, + _fd: &dyn AsRawFd, + ) -> HandlerResult { + Err(std::io::Error::from_raw_os_error(libc::ENOSYS)) + } + /// Handle virtio-fs map file requests. fn fs_backend_map( &mut self, @@ -103,6 +141,25 @@ impl VhostUserFrontendReqHandler for Mutex self.lock().unwrap().handle_config_change() } + /// Handle shared object add operation + fn shared_object_add(&self, uuid: &VhostUserSharedMsg) -> HandlerResult { + self.lock().unwrap().shared_object_add(uuid) + } + + /// Handle shared object remove operation + fn shared_object_remove(&self, uuid: &VhostUserSharedMsg) -> HandlerResult { + self.lock().unwrap().shared_object_remove(uuid) + } + + /// Handle shared object lookup operation + fn shared_object_lookup( + &self, + uuid: &VhostUserSharedMsg, + fd: &dyn AsRawFd, + ) -> HandlerResult { + self.lock().unwrap().shared_object_lookup(uuid, fd) + } + fn fs_backend_map(&self, fs: &VhostUserFSBackendMsg, fd: &dyn AsRawFd) -> HandlerResult { self.lock().unwrap().fs_backend_map(fs, fd) } @@ -230,6 +287,24 @@ impl FrontendReqHandler { .handle_config_change() .map_err(Error::ReqHandlerError) } + Ok(BackendReq::SHARED_OBJECT_ADD) => { + let msg = self.extract_msg_body::(&hdr, size, &buf)?; + self.backend + .shared_object_add(&msg) + .map_err(Error::ReqHandlerError) + } + Ok(BackendReq::SHARED_OBJECT_REMOVE) => { + let msg = self.extract_msg_body::(&hdr, size, &buf)?; + self.backend + .shared_object_remove(&msg) + .map_err(Error::ReqHandlerError) + } + Ok(BackendReq::SHARED_OBJECT_LOOKUP) => { + let msg = self.extract_msg_body::(&hdr, size, &buf)?; + self.backend + .shared_object_lookup(&msg, &files.unwrap()[0]) + .map_err(Error::ReqHandlerError) + } Ok(BackendReq::FS_MAP) => { let msg = self.extract_msg_body::(&hdr, size, &buf)?; // check_attached_files() has validated files @@ -293,7 +368,7 @@ impl FrontendReqHandler { files: &Option>, ) -> Result<()> { match hdr.get_code() { - Ok(BackendReq::FS_MAP | BackendReq::FS_IO) => { + Ok(BackendReq::SHARED_OBJECT_LOOKUP | BackendReq::FS_MAP | BackendReq::FS_IO) => { // Expect a single file is passed. match files { Some(files) if files.len() == 1 => Ok(()), @@ -370,14 +445,46 @@ impl AsRawFd for FrontendReqHandler { mod tests { use super::*; + use std::collections::HashSet; + + use uuid::Uuid; + #[cfg(feature = "vhost-user-backend")] use crate::vhost_user::Backend; #[cfg(feature = "vhost-user-backend")] use std::os::unix::io::FromRawFd; - struct MockFrontendReqHandler {} + struct MockFrontendReqHandler { + shared_objects: HashSet, + } + + impl MockFrontendReqHandler { + fn new() -> Self { + Self { + shared_objects: HashSet::new(), + } + } + } impl VhostUserFrontendReqHandlerMut for MockFrontendReqHandler { + fn shared_object_add(&mut self, uuid: &VhostUserSharedMsg) -> HandlerResult { + Ok(!self.shared_objects.insert(uuid.uuid) as u64) + } + + fn shared_object_remove(&mut self, uuid: &VhostUserSharedMsg) -> HandlerResult { + Ok(!self.shared_objects.remove(&uuid.uuid) as u64) + } + + fn shared_object_lookup( + &mut self, + uuid: &VhostUserSharedMsg, + _fd: &dyn AsRawFd, + ) -> HandlerResult { + if self.shared_objects.get(&uuid.uuid).is_some() { + return Ok(0); + } + Ok(1) + } /// Handle virtio-fs map file requests from the backend. fn fs_backend_map( &mut self, @@ -395,7 +502,7 @@ mod tests { #[test] fn test_new_frontend_req_handler() { - let backend = Arc::new(Mutex::new(MockFrontendReqHandler {})); + let backend = Arc::new(Mutex::new(MockFrontendReqHandler::new())); let mut handler = FrontendReqHandler::new(backend).unwrap(); assert!(handler.get_tx_raw_fd() >= 0); @@ -411,7 +518,7 @@ mod tests { #[cfg(feature = "vhost-user-backend")] #[test] fn test_frontend_backend_req_handler() { - let backend = Arc::new(Mutex::new(MockFrontendReqHandler {})); + let backend = Arc::new(Mutex::new(MockFrontendReqHandler::new())); let mut handler = FrontendReqHandler::new(backend).unwrap(); // SAFETY: Safe because `handler` contains valid fds, and we are @@ -428,6 +535,13 @@ mod tests { let res = handler.handle_request().unwrap(); assert_eq!(res, 0); handler.handle_request().unwrap_err(); + // Testing shared object messages. + assert_eq!(handler.handle_request().unwrap(), 0); + assert_eq!(handler.handle_request().unwrap(), 1); + assert_eq!(handler.handle_request().unwrap(), 0); + assert_eq!(handler.handle_request().unwrap(), 1); + assert_eq!(handler.handle_request().unwrap(), 0); + assert_eq!(handler.handle_request().unwrap(), 1); }); backend @@ -438,6 +552,23 @@ mod tests { backend .fs_backend_unmap(&VhostUserFSBackendMsg::default()) .unwrap(); + + let shobj_msg = VhostUserSharedMsg { + uuid: Uuid::new_v4(), + }; + assert!(backend.shared_object_add(&shobj_msg).is_ok()); + assert!(backend.shared_object_add(&shobj_msg).is_ok()); + assert!(backend.shared_object_lookup(&shobj_msg, &fd).is_ok()); + assert!(backend + .shared_object_lookup( + &VhostUserSharedMsg { + uuid: Uuid::new_v4(), + }, + &fd, + ) + .is_ok()); + assert!(backend.shared_object_remove(&shobj_msg).is_ok()); + assert!(backend.shared_object_remove(&shobj_msg).is_ok()); // Ensure that the handler thread did not panic. assert!(frontend_handler.join().is_ok()); } @@ -445,7 +576,7 @@ mod tests { #[cfg(feature = "vhost-user-backend")] #[test] fn test_frontend_backend_req_handler_with_ack() { - let backend = Arc::new(Mutex::new(MockFrontendReqHandler {})); + let backend = Arc::new(Mutex::new(MockFrontendReqHandler::new())); let mut handler = FrontendReqHandler::new(backend).unwrap(); handler.set_reply_ack_flag(true); @@ -463,6 +594,13 @@ mod tests { let res = handler.handle_request().unwrap(); assert_eq!(res, 0); handler.handle_request().unwrap_err(); + // Testing shared object messages. + assert_eq!(handler.handle_request().unwrap(), 0); + assert_eq!(handler.handle_request().unwrap(), 1); + assert_eq!(handler.handle_request().unwrap(), 0); + assert_eq!(handler.handle_request().unwrap(), 1); + assert_eq!(handler.handle_request().unwrap(), 0); + assert_eq!(handler.handle_request().unwrap(), 1); }); backend.set_reply_ack_flag(true); @@ -472,6 +610,23 @@ mod tests { backend .fs_backend_unmap(&VhostUserFSBackendMsg::default()) .unwrap_err(); + + let shobj_msg = VhostUserSharedMsg { + uuid: Uuid::new_v4(), + }; + assert!(backend.shared_object_add(&shobj_msg).is_ok()); + assert!(backend.shared_object_add(&shobj_msg).is_err()); + assert!(backend.shared_object_lookup(&shobj_msg, &fd).is_ok()); + assert!(backend + .shared_object_lookup( + &VhostUserSharedMsg { + uuid: Uuid::new_v4(), + }, + &fd, + ) + .is_err()); + assert!(backend.shared_object_remove(&shobj_msg).is_ok()); + assert!(backend.shared_object_remove(&shobj_msg).is_err()); // Ensure that the handler thread did not panic. assert!(frontend_handler.join().is_ok()); } diff --git a/vhost/src/vhost_user/message.rs b/vhost/src/vhost_user/message.rs index dd777f9..a30b1a0 100644 --- a/vhost/src/vhost_user/message.rs +++ b/vhost/src/vhost_user/message.rs @@ -15,6 +15,8 @@ use std::io; use std::marker::PhantomData; use std::ops::Deref; +use uuid::Uuid; + use vm_memory::{mmap::NewBitmap, ByteValued, Error as MmapError, FileOffset, MmapRegion}; #[cfg(feature = "xen")] @@ -178,6 +180,8 @@ enum_value! { /// Query the backend for its device status as defined in the VIRTIO /// specification. GET_STATUS = 40, + /// Retrieve a shared object from the device. + GET_SHARED_OBJECT = 41, /// Begin transfer of internal state to/from the backend for migration /// purposes. SET_DEVICE_STATE_FD = 42, @@ -203,6 +207,12 @@ enum_value! { VRING_CALL = 4, /// Indicate that an error occurred on the specific vring. VRING_ERR = 5, + /// Add a virtio shared object. + SHARED_OBJECT_ADD = 6, + /// Remove a virtio shared object. + SHARED_OBJECT_REMOVE = 7, + /// Lookup for a virtio shared object. + SHARED_OBJECT_LOOKUP = 8, // Non-standard message types. /// Virtio-fs draft: map file content into the window. @@ -444,6 +454,8 @@ bitflags! { const STATUS = 0x0001_0000; /// Support Xen mmap. const XEN_MMAP = 0x0002_0000; + /// Support shared objects. + const SHARED_OBJECT = 0x0004_0000; /// Support transferring internal device state. const DEVICE_STATE = 0x0008_0000; } @@ -948,6 +960,25 @@ enum_value! { } } +/// Contains UUID to interact with associated virtio shared object. +#[repr(C)] +#[derive(Clone, Copy, Default)] +pub struct VhostUserSharedMsg { + /// UUID of the shared object + pub uuid: Uuid, +} + +// SAFETY: Safe because VhostUserSharedMsg is a +// fixed-size array internally and there is no +// compiler-inserted padding. +unsafe impl ByteValued for VhostUserSharedMsg {} + +impl VhostUserMsgValidator for VhostUserSharedMsg { + fn is_valid(&self) -> bool { + !(self.uuid.is_nil() || self.uuid.is_max()) + } +} + /// Query/send virtio-fs migration state // Note: this struct is not defined as `packed` in the SPEC and although // it is not necessary, since the struct has no padding, it simplifies