vmm_vhost/
frontend_server.rs

1// Copyright (C) 2019-2021 Alibaba Cloud. All rights reserved.
2// SPDX-License-Identifier: Apache-2.0
3
4use std::fs::File;
5use std::mem;
6
7use base::AsRawDescriptor;
8use zerocopy::FromBytes;
9
10use crate::message::*;
11use crate::BackendReq;
12use crate::Connection;
13use crate::Error;
14use crate::HandlerResult;
15use crate::Result;
16
17/// Trait for vhost-user frontends to respond to requests from the backend.
18///
19/// Each method corresponds to a vhost-user protocol method. See the specification for details.
20pub trait Frontend {
21    /// Handle device configuration change notifications.
22    fn handle_config_change(&mut self) -> HandlerResult<u64> {
23        Err(std::io::Error::from_raw_os_error(libc::ENOSYS))
24    }
25
26    /// Handle shared memory region mapping requests.
27    fn shmem_map(&mut self, _req: &VhostUserMMap, _fd: &dyn AsRawDescriptor) -> HandlerResult<u64> {
28        Err(std::io::Error::from_raw_os_error(libc::ENOSYS))
29    }
30
31    /// Handle shared memory region unmapping requests.
32    fn shmem_unmap(&mut self, _req: &VhostUserMMap) -> HandlerResult<u64> {
33        Err(std::io::Error::from_raw_os_error(libc::ENOSYS))
34    }
35
36    // fn handle_iotlb_msg(&mut self, iotlb: VhostUserIotlb);
37    // fn handle_vring_host_notifier(&mut self, area: VhostUserVringArea, fd: RawDescriptor);
38
39    /// Handle GPU shared memory region mapping requests.
40    fn gpu_map(
41        &mut self,
42        _req: &VhostUserGpuMapMsg,
43        _descriptor: &dyn AsRawDescriptor,
44    ) -> HandlerResult<u64> {
45        Err(std::io::Error::from_raw_os_error(libc::ENOSYS))
46    }
47
48    /// Handle external memory region mapping requests.
49    fn external_map(&mut self, _req: &VhostUserExternalMapMsg) -> HandlerResult<u64> {
50        Err(std::io::Error::from_raw_os_error(libc::ENOSYS))
51    }
52}
53
54/// Handles requests from a vhost-user backend connection by dispatching them to [[Frontend]]
55/// methods.
56pub struct FrontendServer<S: Frontend> {
57    // underlying Unix domain socket for communication
58    pub(crate) sub_sock: Connection<BackendReq>,
59    // Protocol feature VHOST_USER_PROTOCOL_F_REPLY_ACK has been negotiated.
60    reply_ack_negotiated: bool,
61
62    frontend: S,
63}
64
65impl<S: Frontend> FrontendServer<S> {
66    /// Create a server to handle requests from `connection`.
67    pub(crate) fn new(frontend: S, connection: Connection<BackendReq>) -> Result<Self> {
68        Ok(FrontendServer {
69            sub_sock: connection,
70            reply_ack_negotiated: false,
71            frontend,
72        })
73    }
74
75    /// Set the negotiation state of the `VHOST_USER_PROTOCOL_F_REPLY_ACK` protocol feature.
76    ///
77    /// When the `VHOST_USER_PROTOCOL_F_REPLY_ACK` protocol feature has been negotiated,
78    /// the "REPLY_ACK" flag will be set in the message header for every request message.
79    pub fn set_reply_ack_flag(&mut self, enable: bool) {
80        self.reply_ack_negotiated = enable;
81    }
82
83    /// Get the underlying frontend
84    pub fn frontend_mut(&mut self) -> &mut S {
85        &mut self.frontend
86    }
87
88    /// Process the next received request.
89    ///
90    /// The caller needs to:
91    /// - serialize calls to this function
92    /// - decide what to do when errer happens
93    /// - optional recover from failure
94    pub fn handle_request(&mut self) -> Result<u64> {
95        // The underlying communication channel is a Unix domain socket in
96        // stream mode, and recvmsg() is a little tricky here. To successfully
97        // receive attached file descriptors, we need to receive messages and
98        // corresponding attached file descriptors in this way:
99        // . recv messsage header and optional attached file
100        // . validate message header
101        // . recv optional message body and payload according size field in
102        //   message header
103        // . validate message body and optional payload
104        let (hdr, files) = self.sub_sock.recv_header()?;
105        if !hdr.is_valid() || hdr.is_reply() {
106            return Err(Error::InvalidMessage);
107        }
108        self.check_attached_files(&hdr, &files)?;
109        let (buf, extra_files) = self.sub_sock.recv_body_bytes(&hdr)?;
110        if !extra_files.is_empty() {
111            return Err(Error::InvalidMessage);
112        }
113
114        let res = match hdr.get_code() {
115            Ok(BackendReq::CONFIG_CHANGE_MSG) => {
116                self.check_msg_size(&hdr, 0)?;
117                self.frontend
118                    .handle_config_change()
119                    .map_err(Error::ReqHandlerError)
120            }
121            Ok(BackendReq::SHMEM_MAP) => {
122                let msg = self.extract_msg_body::<VhostUserMMap>(&hdr, &buf)?;
123                // check_attached_files() has validated files
124                self.frontend
125                    .shmem_map(&msg, &files[0])
126                    .map_err(Error::ReqHandlerError)
127            }
128            Ok(BackendReq::SHMEM_UNMAP) => {
129                let msg = self.extract_msg_body::<VhostUserMMap>(&hdr, &buf)?;
130                self.frontend
131                    .shmem_unmap(&msg)
132                    .map_err(Error::ReqHandlerError)
133            }
134            Ok(BackendReq::GPU_MAP) => {
135                let msg = self.extract_msg_body::<VhostUserGpuMapMsg>(&hdr, &buf)?;
136                // check_attached_files() has validated files
137                self.frontend
138                    .gpu_map(&msg, &files[0])
139                    .map_err(Error::ReqHandlerError)
140            }
141            Ok(BackendReq::EXTERNAL_MAP) => {
142                let msg = self.extract_msg_body::<VhostUserExternalMapMsg>(&hdr, &buf)?;
143                self.frontend
144                    .external_map(&msg)
145                    .map_err(Error::ReqHandlerError)
146            }
147            _ => Err(Error::InvalidMessage),
148        };
149
150        self.send_reply(&hdr, &res)?;
151
152        res
153    }
154
155    fn check_msg_size(&self, hdr: &VhostUserMsgHeader<BackendReq>, expected: usize) -> Result<()> {
156        if hdr.get_size() as usize != expected {
157            return Err(Error::InvalidMessage);
158        }
159        Ok(())
160    }
161
162    fn check_attached_files(
163        &self,
164        hdr: &VhostUserMsgHeader<BackendReq>,
165        files: &[File],
166    ) -> Result<()> {
167        let expected_num_files = match hdr.get_code().map_err(|_| Error::InvalidMessage)? {
168            // Expect a single file is passed.
169            BackendReq::SHMEM_MAP | BackendReq::GPU_MAP => 1,
170            _ => 0,
171        };
172
173        if files.len() == expected_num_files {
174            Ok(())
175        } else {
176            Err(Error::InvalidMessage)
177        }
178    }
179
180    fn extract_msg_body<T: FromBytes + VhostUserMsgValidator>(
181        &self,
182        hdr: &VhostUserMsgHeader<BackendReq>,
183        buf: &[u8],
184    ) -> Result<T> {
185        self.check_msg_size(hdr, mem::size_of::<T>())?;
186        let msg = T::read_from_bytes(buf).map_err(|_| Error::InvalidMessage)?;
187        if !msg.is_valid() {
188            return Err(Error::InvalidMessage);
189        }
190        Ok(msg)
191    }
192
193    fn new_reply_header<T: Sized>(
194        &self,
195        req: &VhostUserMsgHeader<BackendReq>,
196    ) -> Result<VhostUserMsgHeader<BackendReq>> {
197        Ok(VhostUserMsgHeader::new(
198            req.get_code().map_err(|_| Error::InvalidMessage)?,
199            VhostUserHeaderFlag::REPLY.bits(),
200            mem::size_of::<T>() as u32,
201        ))
202    }
203
204    fn send_reply(
205        &mut self,
206        req: &VhostUserMsgHeader<BackendReq>,
207        res: &Result<u64>,
208    ) -> Result<()> {
209        let code = req.get_code().map_err(|_| Error::InvalidMessage)?;
210        if code == BackendReq::GPU_MAP
211            || code == BackendReq::EXTERNAL_MAP
212            // TODO(fmayle): Remove SHMEM cases once REPLY_ACK negotiation is supported.
213            || code == BackendReq::SHMEM_MAP
214            || code == BackendReq::SHMEM_UNMAP
215            || (self.reply_ack_negotiated && req.is_need_reply())
216        {
217            let hdr = self.new_reply_header::<VhostUserU64>(req)?;
218            let def_err = libc::EINVAL;
219            let val = match res {
220                Ok(n) => *n,
221                Err(e) => match e {
222                    Error::ReqHandlerError(ioerr) => match ioerr.raw_os_error() {
223                        Some(rawerr) => -rawerr as u64,
224                        None => -def_err as u64,
225                    },
226                    _ => -def_err as u64,
227                },
228            };
229            let msg = VhostUserU64::new(val);
230            self.sub_sock.send_message(&hdr, &msg, None)?;
231        }
232        Ok(())
233    }
234}