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<()> {
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<()> {
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<()> {
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<()> {
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<()> {
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,
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) -> 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    pub fn set_reply_ack_flag(&mut self, enable: bool) {
77        self.reply_ack_negotiated = enable;
78    }
79
80    /// Get the underlying frontend
81    pub fn frontend_mut(&mut self) -> &mut S {
82        &mut self.frontend
83    }
84
85    /// Process the next received request.
86    ///
87    /// The caller needs to:
88    /// - serialize calls to this function
89    /// - decide what to do when errer happens
90    /// - optional recover from failure
91    pub fn handle_request(&mut self) -> Result<()> {
92        // The underlying communication channel is a Unix domain socket in
93        // stream mode, and recvmsg() is a little tricky here. To successfully
94        // receive attached file descriptors, we need to receive messages and
95        // corresponding attached file descriptors in this way:
96        // . recv messsage header and optional attached file
97        // . validate message header
98        // . recv optional message body and payload according size field in
99        //   message header
100        // . validate message body and optional payload
101        let (hdr, files) = self.sub_sock.recv_header()?;
102        if !hdr.is_valid() || hdr.is_reply() {
103            return Err(Error::InvalidMessage);
104        }
105        self.check_attached_files(&hdr, &files)?;
106        let (buf, extra_files) = self.sub_sock.recv_body_bytes(&hdr)?;
107        if !extra_files.is_empty() {
108            return Err(Error::InvalidMessage);
109        }
110
111        let res = match hdr.get_code() {
112            Ok(BackendReq::CONFIG_CHANGE_MSG) => {
113                self.check_msg_size(&hdr, 0)?;
114                self.frontend
115                    .handle_config_change()
116                    .map_err(Error::ReqHandlerError)
117            }
118            Ok(BackendReq::SHMEM_MAP) => {
119                let msg = self.extract_msg_body::<VhostUserMMap>(&hdr, &buf)?;
120                // check_attached_files() has validated files
121                self.frontend
122                    .shmem_map(&msg, &files[0])
123                    .map_err(Error::ReqHandlerError)
124            }
125            Ok(BackendReq::SHMEM_UNMAP) => {
126                let msg = self.extract_msg_body::<VhostUserMMap>(&hdr, &buf)?;
127                self.frontend
128                    .shmem_unmap(&msg)
129                    .map_err(Error::ReqHandlerError)
130            }
131            Ok(BackendReq::GPU_MAP) => {
132                let msg = self.extract_msg_body::<VhostUserGpuMapMsg>(&hdr, &buf)?;
133                // check_attached_files() has validated files
134                self.frontend
135                    .gpu_map(&msg, &files[0])
136                    .map_err(Error::ReqHandlerError)
137            }
138            Ok(BackendReq::EXTERNAL_MAP) => {
139                let msg = self.extract_msg_body::<VhostUserExternalMapMsg>(&hdr, &buf)?;
140                self.frontend
141                    .external_map(&msg)
142                    .map_err(Error::ReqHandlerError)
143            }
144            _ => Err(Error::InvalidMessage),
145        };
146
147        self.send_reply(&hdr, &res)?;
148
149        res
150    }
151
152    fn check_msg_size(&self, hdr: &VhostUserMsgHeader, expected: usize) -> Result<()> {
153        if hdr.get_size() as usize != expected {
154            return Err(Error::InvalidMessage);
155        }
156        Ok(())
157    }
158
159    fn check_attached_files(&self, hdr: &VhostUserMsgHeader, files: &[File]) -> Result<()> {
160        let expected_num_files = match hdr.get_code().map_err(|_| Error::InvalidMessage)? {
161            // Expect a single file is passed.
162            BackendReq::SHMEM_MAP | BackendReq::GPU_MAP => 1,
163            _ => 0,
164        };
165
166        if files.len() == expected_num_files {
167            Ok(())
168        } else {
169            Err(Error::InvalidMessage)
170        }
171    }
172
173    fn extract_msg_body<T: FromBytes + VhostUserMsgValidator>(
174        &self,
175        hdr: &VhostUserMsgHeader,
176        buf: &[u8],
177    ) -> Result<T> {
178        self.check_msg_size(hdr, mem::size_of::<T>())?;
179        let msg = T::read_from_bytes(buf).map_err(|_| Error::InvalidMessage)?;
180        if !msg.is_valid() {
181            return Err(Error::InvalidMessage);
182        }
183        Ok(msg)
184    }
185
186    fn new_reply_header<T: Sized>(&self, req: &VhostUserMsgHeader) -> Result<VhostUserMsgHeader> {
187        Ok(VhostUserMsgHeader::new_reply_header(
188            req.get_code::<BackendReq>()
189                .map_err(|_| Error::InvalidMessage)?,
190            mem::size_of::<T>() as u32,
191        ))
192    }
193
194    fn send_reply(&mut self, req: &VhostUserMsgHeader, res: &Result<()>) -> Result<()> {
195        let code = req
196            .get_code::<BackendReq>()
197            .map_err(|_| Error::InvalidMessage)?;
198        if code == BackendReq::GPU_MAP
199            || code == BackendReq::EXTERNAL_MAP
200            || (self.reply_ack_negotiated && req.is_need_reply())
201        {
202            let hdr = self.new_reply_header::<VhostUserU64>(req)?;
203            let def_err = libc::EINVAL;
204            let val = match res {
205                Ok(()) => 0,
206                Err(e) => match e {
207                    Error::ReqHandlerError(ioerr) => match ioerr.raw_os_error() {
208                        Some(rawerr) => -rawerr as u64,
209                        None => -def_err as u64,
210                    },
211                    _ => -def_err as u64,
212                },
213            };
214            let msg = VhostUserU64::new(val);
215            self.sub_sock.send_message(&hdr, &msg, None)?;
216        }
217        Ok(())
218    }
219}