devices/virtio/vhost_user_backend/
vsock.rs

1// Copyright 2021 The ChromiumOS Authors
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use std::convert::TryInto;
6use std::fs::File;
7use std::fs::OpenOptions;
8use std::mem::size_of;
9use std::num::Wrapping;
10use std::os::unix::fs::OpenOptionsExt;
11use std::path::Path;
12use std::str;
13
14use anyhow::Context;
15use argh::FromArgs;
16use base::AsRawDescriptor;
17use base::Event;
18use base::RawDescriptor;
19use base::SafeDescriptor;
20use cros_async::Executor;
21use data_model::Le64;
22use vhost::Vhost;
23use vhost::Vsock;
24use vm_memory::GuestMemory;
25use vmm_vhost::connection::Connection;
26use vmm_vhost::message::BackendReq;
27use vmm_vhost::message::VhostSharedMemoryRegion;
28use vmm_vhost::message::VhostUserConfigFlags;
29use vmm_vhost::message::VhostUserInflight;
30use vmm_vhost::message::VhostUserMemoryRegion;
31use vmm_vhost::message::VhostUserMigrationPhase;
32use vmm_vhost::message::VhostUserProtocolFeatures;
33use vmm_vhost::message::VhostUserSingleMemoryRegion;
34use vmm_vhost::message::VhostUserTransferDirection;
35use vmm_vhost::message::VhostUserVringAddrFlags;
36use vmm_vhost::message::VhostUserVringState;
37use vmm_vhost::Error;
38use vmm_vhost::Result;
39use vmm_vhost::VHOST_USER_F_PROTOCOL_FEATURES;
40use zerocopy::IntoBytes;
41
42use super::BackendConnection;
43use crate::virtio::device_constants::vsock::NUM_QUEUES;
44use crate::virtio::vhost_user_backend::handler::vmm_va_to_gpa;
45use crate::virtio::vhost_user_backend::handler::MappingInfo;
46use crate::virtio::vhost_user_backend::handler::VhostUserRegularOps;
47use crate::virtio::vhost_user_backend::VhostUserDeviceBuilder;
48use crate::virtio::Queue;
49use crate::virtio::QueueConfig;
50
51const EVENT_QUEUE: usize = NUM_QUEUES - 1;
52
53struct VsockBackend {
54    queues: [QueueConfig; NUM_QUEUES],
55    vmm_maps: Option<Vec<MappingInfo>>,
56    mem: Option<GuestMemory>,
57
58    handle: Vsock,
59    cid: u64,
60    protocol_features: VhostUserProtocolFeatures,
61}
62
63/// A vhost-vsock device which handle is already opened. This allows the parent process to open the
64/// vhost-vsock device, create this structure, and pass it to the child process so it doesn't need
65/// the rights to open the vhost-vsock device itself.
66pub struct VhostUserVsockDevice {
67    cid: u64,
68    handle: Vsock,
69}
70
71impl VhostUserVsockDevice {
72    pub fn new<P: AsRef<Path>>(cid: u64, vhost_device: P) -> anyhow::Result<Self> {
73        let handle = Vsock::new(
74            OpenOptions::new()
75                .read(true)
76                .write(true)
77                .custom_flags(libc::O_CLOEXEC | libc::O_NONBLOCK)
78                .open(vhost_device.as_ref())
79                .with_context(|| {
80                    format!(
81                        "failed to open vhost-vsock device {}",
82                        vhost_device.as_ref().display()
83                    )
84                })?,
85        );
86
87        Ok(Self { cid, handle })
88    }
89}
90
91impl AsRawDescriptor for VhostUserVsockDevice {
92    fn as_raw_descriptor(&self) -> base::RawDescriptor {
93        self.handle.as_raw_descriptor()
94    }
95}
96
97impl VhostUserDeviceBuilder for VhostUserVsockDevice {
98    fn build(self: Box<Self>, _ex: &Executor) -> anyhow::Result<Box<dyn vmm_vhost::Backend>> {
99        let backend = VsockBackend {
100            queues: [
101                QueueConfig::new(Queue::MAX_SIZE, 0),
102                QueueConfig::new(Queue::MAX_SIZE, 0),
103                QueueConfig::new(Queue::MAX_SIZE, 0),
104            ],
105            vmm_maps: None,
106            mem: None,
107            handle: self.handle,
108            cid: self.cid,
109            protocol_features: VhostUserProtocolFeatures::MQ | VhostUserProtocolFeatures::CONFIG,
110        };
111
112        Ok(Box::new(backend))
113    }
114}
115
116fn convert_vhost_error(err: vhost::Error) -> Error {
117    use vhost::Error::*;
118    match err {
119        IoctlError(e) => Error::ReqHandlerError(e),
120        _ => Error::BackendInternalError,
121    }
122}
123
124impl vmm_vhost::Backend for VsockBackend {
125    fn set_owner(&mut self) -> Result<()> {
126        self.handle.set_owner().map_err(convert_vhost_error)
127    }
128
129    fn reset_owner(&mut self) -> Result<()> {
130        self.handle.reset_owner().map_err(convert_vhost_error)
131    }
132
133    fn get_features(&mut self) -> Result<u64> {
134        // Add the vhost-user features that we support.
135        let features = self.handle.get_features().map_err(convert_vhost_error)?
136            | 1 << VHOST_USER_F_PROTOCOL_FEATURES;
137        Ok(features)
138    }
139
140    fn set_features(&mut self, features: u64) -> Result<()> {
141        // Unset the vhost-user feature flags as they are not supported by the underlying vhost
142        // device.
143        let features = features & !(1 << VHOST_USER_F_PROTOCOL_FEATURES);
144        self.handle
145            .set_features(features)
146            .map_err(convert_vhost_error)
147    }
148
149    fn get_protocol_features(&mut self) -> Result<VhostUserProtocolFeatures> {
150        Ok(self.protocol_features)
151    }
152
153    fn set_protocol_features(&mut self, features: u64) -> Result<()> {
154        let unrequested_features = features & !self.protocol_features.bits();
155        if unrequested_features != 0 {
156            Err(Error::InvalidParam("unsupported protocol feature"))
157        } else {
158            Ok(())
159        }
160    }
161
162    fn set_mem_table(
163        &mut self,
164        contexts: &[VhostUserMemoryRegion],
165        files: Vec<File>,
166    ) -> Result<()> {
167        let (guest_mem, vmm_maps) = VhostUserRegularOps::set_mem_table(contexts, files)?;
168
169        self.handle
170            .set_mem_table(&guest_mem)
171            .map_err(convert_vhost_error)?;
172
173        self.mem = Some(guest_mem);
174        self.vmm_maps = Some(vmm_maps);
175
176        Ok(())
177    }
178
179    fn get_queue_num(&mut self) -> Result<u64> {
180        Ok(NUM_QUEUES as u64)
181    }
182
183    fn set_vring_num(&mut self, index: u32, num: u32) -> Result<()> {
184        if index >= NUM_QUEUES as u32 || num == 0 || num > Queue::MAX_SIZE.into() {
185            return Err(Error::InvalidParam(
186                "set_vring_num: vring index or size out of range",
187            ));
188        }
189
190        // We checked these values already.
191        let index = index as usize;
192        let num = num as u16;
193        self.queues[index].set_size(num);
194
195        // The last vq is an event-only vq that is not handled by the kernel.
196        if index == EVENT_QUEUE {
197            return Ok(());
198        }
199
200        self.handle
201            .set_vring_num(index, num)
202            .map_err(convert_vhost_error)
203    }
204
205    fn set_vring_addr(
206        &mut self,
207        index: u32,
208        flags: VhostUserVringAddrFlags,
209        descriptor: u64,
210        used: u64,
211        available: u64,
212        log: u64,
213    ) -> Result<()> {
214        if index >= NUM_QUEUES as u32 {
215            return Err(Error::InvalidParam("set_vring_addr: index out of range"));
216        }
217
218        let index = index as usize;
219
220        let mem = self
221            .mem
222            .as_ref()
223            .ok_or(Error::InvalidParam("set_vring_addr: could not get mem"))?;
224        let maps = self.vmm_maps.as_ref().ok_or(Error::InvalidParam(
225            "set_vring_addr: could not get vmm_maps",
226        ))?;
227
228        let queue = &mut self.queues[index];
229        queue.set_desc_table(vmm_va_to_gpa(maps, descriptor)?);
230        queue.set_avail_ring(vmm_va_to_gpa(maps, available)?);
231        queue.set_used_ring(vmm_va_to_gpa(maps, used)?);
232        let log_addr = if flags.contains(VhostUserVringAddrFlags::VHOST_VRING_F_LOG) {
233            vmm_va_to_gpa(maps, log).map(Some)?
234        } else {
235            None
236        };
237
238        if index == EVENT_QUEUE {
239            return Ok(());
240        }
241
242        self.handle
243            .set_vring_addr(
244                mem,
245                queue.size(),
246                index,
247                flags.bits(),
248                queue.desc_table(),
249                queue.used_ring(),
250                queue.avail_ring(),
251                log_addr,
252            )
253            .map_err(convert_vhost_error)
254    }
255
256    fn set_vring_base(&mut self, index: u32, base: u32) -> Result<()> {
257        if index >= NUM_QUEUES as u32 {
258            return Err(Error::InvalidParam("set_vring_base: index out of range"));
259        }
260
261        let index = index as usize;
262        let base = base as u16;
263
264        let queue = &mut self.queues[index];
265        queue.set_next_avail(Wrapping(base));
266        queue.set_next_used(Wrapping(base));
267
268        if index == EVENT_QUEUE {
269            return Ok(());
270        }
271
272        self.handle
273            .set_vring_base(index, base)
274            .map_err(convert_vhost_error)
275    }
276
277    fn get_vring_base(&mut self, index: u32) -> Result<VhostUserVringState> {
278        if index >= NUM_QUEUES as u32 {
279            return Err(Error::InvalidParam("get_vring_base: index out of range"));
280        }
281
282        let index = index as usize;
283        let next_avail = if index == EVENT_QUEUE {
284            self.queues[index].next_avail().0
285        } else {
286            self.handle
287                .get_vring_base(index)
288                .map_err(convert_vhost_error)?
289        };
290
291        Ok(VhostUserVringState::new(index as u32, next_avail.into()))
292    }
293
294    fn set_vring_kick(&mut self, index: u8, fd: Option<File>) -> Result<()> {
295        if index >= NUM_QUEUES as u8 {
296            return Err(Error::InvalidParam("set_vring_kick: index out of range"));
297        }
298
299        let file = fd.ok_or(Error::InvalidParam("set_vring_kick: missing fd"))?;
300        let event = Event::from(SafeDescriptor::from(file));
301        let index = usize::from(index);
302        if index != EVENT_QUEUE {
303            self.handle
304                .set_vring_kick(index, &event)
305                .map_err(convert_vhost_error)?;
306        }
307
308        Ok(())
309    }
310
311    fn set_vring_call(&mut self, index: u8, fd: Option<File>) -> Result<()> {
312        if index >= NUM_QUEUES as u8 {
313            return Err(Error::InvalidParam("set_vring_call: index out of range"));
314        }
315
316        let file = fd.ok_or(Error::InvalidParam("set_vring_call: missing fd"))?;
317        let event = Event::from(SafeDescriptor::from(file));
318        let index = usize::from(index);
319        if index != EVENT_QUEUE {
320            self.handle
321                .set_vring_call(index, &event)
322                .map_err(convert_vhost_error)?;
323        }
324
325        Ok(())
326    }
327
328    fn set_vring_err(&mut self, index: u8, fd: Option<File>) -> Result<()> {
329        if index >= NUM_QUEUES as u8 {
330            return Err(Error::InvalidParam("set_vring_err: index out of range"));
331        }
332
333        let index = usize::from(index);
334        let file = fd.ok_or(Error::InvalidParam("set_vring_err: missing fd"))?;
335
336        let event = Event::from(SafeDescriptor::from(file));
337
338        if index == EVENT_QUEUE {
339            return Ok(());
340        }
341
342        self.handle
343            .set_vring_err(index, &event)
344            .map_err(convert_vhost_error)
345    }
346
347    fn set_vring_enable(&mut self, index: u32, enable: bool) -> Result<()> {
348        if index >= NUM_QUEUES as u32 {
349            return Err(Error::InvalidParam("vring index out of range"));
350        }
351
352        self.queues[index as usize].set_ready(enable);
353
354        if index == (EVENT_QUEUE) as u32 {
355            return Ok(());
356        }
357
358        if self.queues[..EVENT_QUEUE].iter().all(|q| q.ready()) {
359            // All queues are ready.  Start the device.
360            self.handle.set_cid(self.cid).map_err(convert_vhost_error)?;
361            self.handle.start().map_err(convert_vhost_error)
362        } else if !enable {
363            // If we just disabled a vring then stop the device.
364            self.handle.stop().map_err(convert_vhost_error)
365        } else {
366            Ok(())
367        }
368    }
369
370    fn get_config(
371        &mut self,
372        offset: u32,
373        size: u32,
374        _flags: VhostUserConfigFlags,
375    ) -> Result<Vec<u8>> {
376        let start: usize = offset
377            .try_into()
378            .map_err(|_| Error::InvalidParam("offset does not fit in usize"))?;
379        let end: usize = offset
380            .checked_add(size)
381            .and_then(|e| e.try_into().ok())
382            .ok_or(Error::InvalidParam("offset + size does not fit in usize"))?;
383
384        if start >= size_of::<Le64>() || end > size_of::<Le64>() {
385            return Err(Error::InvalidParam(
386                "get_config: offset and/or size out of range",
387            ));
388        }
389
390        Ok(Le64::from(self.cid).as_bytes()[start..end].to_vec())
391    }
392
393    fn set_config(
394        &mut self,
395        _offset: u32,
396        _buf: &[u8],
397        _flags: VhostUserConfigFlags,
398    ) -> Result<()> {
399        Err(Error::InvalidOperation)
400    }
401
402    fn set_backend_req_fd(&mut self, _vu_req: Connection<BackendReq>) {
403        // We didn't set VhostUserProtocolFeatures::BACKEND_REQ
404        unreachable!("unexpected set_backend_req_fd");
405    }
406
407    fn get_inflight_fd(
408        &mut self,
409        _inflight: &VhostUserInflight,
410    ) -> Result<(VhostUserInflight, File)> {
411        Err(Error::InvalidOperation)
412    }
413
414    fn set_inflight_fd(&mut self, _inflight: &VhostUserInflight, _file: File) -> Result<()> {
415        Err(Error::InvalidOperation)
416    }
417
418    fn get_max_mem_slots(&mut self) -> Result<u64> {
419        Err(Error::InvalidOperation)
420    }
421
422    fn add_mem_region(&mut self, _region: &VhostUserSingleMemoryRegion, _fd: File) -> Result<()> {
423        Err(Error::InvalidOperation)
424    }
425
426    fn remove_mem_region(&mut self, _region: &VhostUserSingleMemoryRegion) -> Result<()> {
427        Err(Error::InvalidOperation)
428    }
429
430    fn set_device_state_fd(
431        &mut self,
432        _transfer_direction: VhostUserTransferDirection,
433        _migration_phase: VhostUserMigrationPhase,
434        _fd: File,
435    ) -> Result<Option<File>> {
436        Err(Error::InvalidOperation)
437    }
438
439    fn check_device_state(&mut self) -> Result<()> {
440        Err(Error::InvalidOperation)
441    }
442
443    fn get_shared_memory_regions(&mut self) -> Result<Vec<VhostSharedMemoryRegion>> {
444        Ok(vec![])
445    }
446}
447
448#[derive(FromArgs)]
449#[argh(subcommand, name = "vsock")]
450/// Vsock device
451pub struct Options {
452    #[argh(option, arg_name = "PATH", hidden_help)]
453    /// deprecated - please use --socket-path instead
454    socket: Option<String>,
455    #[argh(option, arg_name = "PATH")]
456    /// path to the vhost-user socket to bind to.
457    /// If this flag is set, --fd cannot be specified.
458    socket_path: Option<String>,
459    #[argh(option, arg_name = "FD")]
460    /// file descriptor of a connected vhost-user socket.
461    /// If this flag is set, --socket-path cannot be specified.
462    fd: Option<RawDescriptor>,
463
464    #[argh(option, arg_name = "INT")]
465    /// the vsock context id for this device
466    cid: u64,
467    #[argh(
468        option,
469        default = "String::from(\"/dev/vhost-vsock\")",
470        arg_name = "PATH"
471    )]
472    /// path to the vhost-vsock control socket
473    vhost_socket: String,
474}
475
476/// Returns an error if the given `args` is invalid or the device fails to run.
477pub fn run_vsock_device(opts: Options) -> anyhow::Result<()> {
478    let ex = Executor::new().context("failed to create executor")?;
479
480    let conn =
481        BackendConnection::from_opts(opts.socket.as_deref(), opts.socket_path.as_deref(), opts.fd)?;
482
483    let vsock_device = Box::new(VhostUserVsockDevice::new(opts.cid, opts.vhost_socket)?);
484
485    conn.run_device(ex, vsock_device)
486}