1 // Copyright (C) 2020 Alibaba Cloud. All rights reserved. 2 // SPDX-License-Identifier: Apache-2.0 3 4 use std::mem; 5 use std::string::ToString; 6 7 use base::AsRawDescriptor; 8 use base::RawDescriptor; 9 use zerocopy::AsBytes; 10 11 use crate::message::*; 12 use crate::BackendReq; 13 use crate::Connection; 14 use crate::Error; 15 use crate::Frontend; 16 use crate::HandlerResult; 17 use crate::Result; 18 19 /// Client for a vhost-user frontend. Allows a backend to send requests to the frontend. 20 pub struct FrontendClient { 21 sock: Connection<BackendReq>, 22 23 // Protocol feature VHOST_USER_PROTOCOL_F_REPLY_ACK has been negotiated. 24 reply_ack_negotiated: bool, 25 26 // whether the connection has encountered any failure 27 error: Option<i32>, 28 } 29 30 impl FrontendClient { 31 /// Create a new instance from the given connection. new(ep: Connection<BackendReq>) -> Self32 pub fn new(ep: Connection<BackendReq>) -> Self { 33 FrontendClient { 34 sock: ep, 35 reply_ack_negotiated: false, 36 error: None, 37 } 38 } 39 send_message<T>( &mut self, request: BackendReq, msg: &T, fds: Option<&[RawDescriptor]>, ) -> HandlerResult<u64> where T: AsBytes,40 fn send_message<T>( 41 &mut self, 42 request: BackendReq, 43 msg: &T, 44 fds: Option<&[RawDescriptor]>, 45 ) -> HandlerResult<u64> 46 where 47 T: AsBytes, 48 { 49 let len = mem::size_of::<T>(); 50 let mut hdr = VhostUserMsgHeader::new(request, 0, len as u32); 51 if self.reply_ack_negotiated { 52 hdr.set_need_reply(true); 53 } 54 self.sock 55 .send_message(&hdr, msg, fds) 56 .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.to_string()))?; 57 58 self.wait_for_reply(&hdr) 59 .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.to_string())) 60 } 61 wait_for_reply(&mut self, hdr: &VhostUserMsgHeader<BackendReq>) -> Result<u64>62 fn wait_for_reply(&mut self, hdr: &VhostUserMsgHeader<BackendReq>) -> Result<u64> { 63 let code = hdr.get_code().map_err(|_| Error::InvalidMessage)?; 64 if code != BackendReq::SHMEM_MAP 65 && code != BackendReq::SHMEM_UNMAP 66 && code != BackendReq::GPU_MAP 67 && code != BackendReq::EXTERNAL_MAP 68 && !self.reply_ack_negotiated 69 { 70 return Ok(0); 71 } 72 73 let (reply, body, rfds) = self.sock.recv_message::<VhostUserU64>()?; 74 if !reply.is_reply_for(hdr) || !rfds.is_empty() || !body.is_valid() { 75 return Err(Error::InvalidMessage); 76 } 77 if body.value != 0 { 78 return Err(Error::FrontendInternalError); 79 } 80 81 Ok(body.value) 82 } 83 84 /// Set the negotiation state of the `VHOST_USER_PROTOCOL_F_REPLY_ACK` protocol feature. 85 /// 86 /// When the `VHOST_USER_PROTOCOL_F_REPLY_ACK` protocol feature has been negotiated, the 87 /// "REPLY_ACK" flag will be set in the message header for every backend to frontend request 88 /// message. set_reply_ack_flag(&mut self, enable: bool)89 pub fn set_reply_ack_flag(&mut self, enable: bool) { 90 self.reply_ack_negotiated = enable; 91 } 92 93 /// Mark connection as failed with specified error code. set_failed(&mut self, error: i32)94 pub fn set_failed(&mut self, error: i32) { 95 self.error = Some(error); 96 } 97 } 98 99 impl Frontend for FrontendClient { 100 /// Handle shared memory region mapping requests. shmem_map( &mut self, req: &VhostUserShmemMapMsg, fd: &dyn AsRawDescriptor, ) -> HandlerResult<u64>101 fn shmem_map( 102 &mut self, 103 req: &VhostUserShmemMapMsg, 104 fd: &dyn AsRawDescriptor, 105 ) -> HandlerResult<u64> { 106 self.send_message(BackendReq::SHMEM_MAP, req, Some(&[fd.as_raw_descriptor()])) 107 } 108 109 /// Handle shared memory region unmapping requests. shmem_unmap(&mut self, req: &VhostUserShmemUnmapMsg) -> HandlerResult<u64>110 fn shmem_unmap(&mut self, req: &VhostUserShmemUnmapMsg) -> HandlerResult<u64> { 111 self.send_message(BackendReq::SHMEM_UNMAP, req, None) 112 } 113 114 /// Handle config change requests. handle_config_change(&mut self) -> HandlerResult<u64>115 fn handle_config_change(&mut self) -> HandlerResult<u64> { 116 self.send_message(BackendReq::CONFIG_CHANGE_MSG, &VhostUserEmptyMessage, None) 117 } 118 119 /// Handle GPU shared memory region mapping requests. gpu_map( &mut self, req: &VhostUserGpuMapMsg, descriptor: &dyn AsRawDescriptor, ) -> HandlerResult<u64>120 fn gpu_map( 121 &mut self, 122 req: &VhostUserGpuMapMsg, 123 descriptor: &dyn AsRawDescriptor, 124 ) -> HandlerResult<u64> { 125 self.send_message( 126 BackendReq::GPU_MAP, 127 req, 128 Some(&[descriptor.as_raw_descriptor()]), 129 ) 130 } 131 132 /// Handle external memory region mapping requests. external_map(&mut self, req: &VhostUserExternalMapMsg) -> HandlerResult<u64>133 fn external_map(&mut self, req: &VhostUserExternalMapMsg) -> HandlerResult<u64> { 134 self.send_message(BackendReq::EXTERNAL_MAP, req, None) 135 } 136 } 137 138 #[cfg(test)] 139 mod tests { 140 use super::*; 141 142 #[test] test_backend_req_set_failed()143 fn test_backend_req_set_failed() { 144 let (p1, _p2) = Connection::pair().unwrap(); 145 let mut frontend_client = FrontendClient::new(p1); 146 147 assert!(frontend_client.error.is_none()); 148 frontend_client.set_failed(libc::EAGAIN); 149 assert_eq!(frontend_client.error, Some(libc::EAGAIN)); 150 } 151 } 152