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