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