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::GuestAddress;
25use vm_memory::GuestMemory;
26use vmm_vhost::connection::Connection;
27use vmm_vhost::message::VhostUserConfigFlags;
28use vmm_vhost::message::VhostUserInflight;
29use vmm_vhost::message::VhostUserMemoryRegion;
30use vmm_vhost::message::VhostUserMigrationPhase;
31use vmm_vhost::message::VhostUserProtocolFeatures;
32use vmm_vhost::message::VhostUserSingleMemoryRegion;
33use vmm_vhost::message::VhostUserTransferDirection;
34use vmm_vhost::message::VhostUserVringAddrFlags;
35use vmm_vhost::message::VhostUserVringState;
36use vmm_vhost::Error;
37use vmm_vhost::Result;
38use vmm_vhost::SharedMemoryRegion;
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 VringConfig {
54    kick_fd: Option<File>,
55    call_fd: Option<File>,
56    err_fd: Option<File>,
57    flags: VhostUserVringAddrFlags,
58    log_addr: Option<GuestAddress>,
59}
60
61impl Default for VringConfig {
62    fn default() -> Self {
63        Self {
64            kick_fd: None,
65            call_fd: None,
66            err_fd: None,
67            flags: VhostUserVringAddrFlags::empty(),
68            log_addr: None,
69        }
70    }
71}
72
73struct VringState {
74    queue: QueueConfig,
75    config: VringConfig,
76}
77
78impl Default for VringState {
79    fn default() -> Self {
80        Self {
81            queue: QueueConfig::new(Queue::MAX_SIZE, 0),
82            config: VringConfig::default(),
83        }
84    }
85}
86
87struct VsockBackend {
88    vrings: [VringState; NUM_QUEUES],
89    vmm_maps: Option<Vec<MappingInfo>>,
90    mem: Option<GuestMemory>,
91
92    handle: Vsock,
93    cid: u64,
94    protocol_features: VhostUserProtocolFeatures,
95}
96
97/// A vhost-vsock device which handle is already opened. This allows the parent process to open the
98/// vhost-vsock device, create this structure, and pass it to the child process so it doesn't need
99/// the rights to open the vhost-vsock device itself.
100pub struct VhostUserVsockDevice {
101    cid: u64,
102    handle: Vsock,
103}
104
105impl VhostUserVsockDevice {
106    pub fn new<P: AsRef<Path>>(cid: u64, vhost_device: P) -> anyhow::Result<Self> {
107        let handle = Vsock::new(
108            OpenOptions::new()
109                .read(true)
110                .write(true)
111                .custom_flags(libc::O_CLOEXEC | libc::O_NONBLOCK)
112                .open(vhost_device.as_ref())
113                .with_context(|| {
114                    format!(
115                        "failed to open vhost-vsock device {}",
116                        vhost_device.as_ref().display()
117                    )
118                })?,
119        );
120
121        Ok(Self { cid, handle })
122    }
123}
124
125impl AsRawDescriptor for VhostUserVsockDevice {
126    fn as_raw_descriptor(&self) -> base::RawDescriptor {
127        self.handle.as_raw_descriptor()
128    }
129}
130
131impl VhostUserDeviceBuilder for VhostUserVsockDevice {
132    fn build(self: Box<Self>, _ex: &Executor) -> anyhow::Result<Box<dyn vmm_vhost::Backend>> {
133        let backend = VsockBackend {
134            vrings: Default::default(),
135            vmm_maps: None,
136            mem: None,
137            handle: self.handle,
138            cid: self.cid,
139            protocol_features: VhostUserProtocolFeatures::MQ | VhostUserProtocolFeatures::CONFIG,
140        };
141
142        Ok(Box::new(backend))
143    }
144}
145
146fn convert_vhost_error(err: vhost::Error) -> Error {
147    use vhost::Error::*;
148    match err {
149        IoctlError(e) => Error::ReqHandlerError(e),
150        _ => Error::BackendInternalError,
151    }
152}
153
154fn program_fd<F>(handle: &Vsock, fd: Option<&File>, index: usize, f: F) -> Result<()>
155where
156    F: FnOnce(&Vsock, usize, &Event) -> std::result::Result<(), vhost::Error>,
157{
158    if let Some(file) = fd {
159        let cloned_file = file.try_clone().map_err(Error::ReqHandlerError)?;
160        let event = Event::from(SafeDescriptor::from(cloned_file));
161        f(handle, index, &event).map_err(convert_vhost_error)?;
162    }
163    Ok(())
164}
165
166impl VsockBackend {
167    fn validate_addresses(
168        mem: &GuestMemory,
169        queue_size: u16,
170        desc_addr: GuestAddress,
171        avail_addr: GuestAddress,
172        used_addr: GuestAddress,
173        log_addr: Option<GuestAddress>,
174    ) -> Result<()> {
175        if queue_size == 0 || !queue_size.is_power_of_two() {
176            return Err(Error::InvalidParam("invalid queue size"));
177        }
178
179        let queue_size = usize::from(queue_size);
180
181        let desc_table_size = 16 * queue_size;
182        mem.get_slice_at_addr(desc_addr, desc_table_size)
183            .map_err(|_| Error::InvalidParam("invalid descriptor table address"))?;
184
185        let used_ring_size = 6 + 8 * queue_size;
186        mem.get_slice_at_addr(used_addr, used_ring_size)
187            .map_err(|_| Error::InvalidParam("invalid used ring address"))?;
188
189        let avail_ring_size = 6 + 2 * queue_size;
190        mem.get_slice_at_addr(avail_addr, avail_ring_size)
191            .map_err(|_| Error::InvalidParam("invalid available ring address"))?;
192
193        if let Some(a) = log_addr {
194            mem.get_host_address(a)
195                .map_err(|_| Error::InvalidParam("invalid log address"))?;
196        }
197
198        Ok(())
199    }
200
201    fn program_queues_to_kernel(&mut self) -> Result<()> {
202        let mem = self
203            .mem
204            .as_ref()
205            .ok_or(Error::InvalidParam("program_queues: guest memory not set"))?;
206
207        for index in 0..EVENT_QUEUE {
208            let vring = &self.vrings[index];
209            let queue = &vring.queue;
210            let config = &vring.config;
211
212            self.handle
213                .set_vring_num(index, queue.size())
214                .map_err(convert_vhost_error)?;
215
216            let flags = config.flags;
217            let log_addr = config.log_addr;
218            self.handle
219                .set_vring_addr(
220                    mem,
221                    queue.size(),
222                    index,
223                    flags.bits(),
224                    queue.desc_table(),
225                    queue.used_ring(),
226                    queue.avail_ring(),
227                    log_addr,
228                )
229                .map_err(convert_vhost_error)?;
230
231            self.handle
232                .set_vring_base(index, queue.next_avail().0)
233                .map_err(convert_vhost_error)?;
234
235            program_fd(
236                &self.handle,
237                config.kick_fd.as_ref(),
238                index,
239                Vsock::set_vring_kick,
240            )?;
241            program_fd(
242                &self.handle,
243                config.call_fd.as_ref(),
244                index,
245                Vsock::set_vring_call,
246            )?;
247            program_fd(
248                &self.handle,
249                config.err_fd.as_ref(),
250                index,
251                Vsock::set_vring_err,
252            )?;
253        }
254        Ok(())
255    }
256}
257
258impl vmm_vhost::Backend for VsockBackend {
259    fn set_owner(&mut self) -> Result<()> {
260        self.handle.set_owner().map_err(convert_vhost_error)
261    }
262
263    fn reset_owner(&mut self) -> Result<()> {
264        self.handle.reset_owner().map_err(convert_vhost_error)
265    }
266
267    fn get_features(&mut self) -> Result<u64> {
268        // Add the vhost-user features that we support.
269        let features = self.handle.get_features().map_err(convert_vhost_error)?
270            | 1 << VHOST_USER_F_PROTOCOL_FEATURES;
271        Ok(features)
272    }
273
274    fn set_features(&mut self, features: u64) -> Result<()> {
275        // Unset the vhost-user feature flags as they are not supported by the underlying vhost
276        // device.
277        let features = features & !(1 << VHOST_USER_F_PROTOCOL_FEATURES);
278        self.handle
279            .set_features(features)
280            .map_err(convert_vhost_error)
281    }
282
283    fn get_protocol_features(&mut self) -> Result<VhostUserProtocolFeatures> {
284        Ok(self.protocol_features)
285    }
286
287    fn set_protocol_features(&mut self, features: u64) -> Result<()> {
288        let unrequested_features = features & !self.protocol_features.bits();
289        if unrequested_features != 0 {
290            Err(Error::InvalidParam("unsupported protocol feature"))
291        } else {
292            Ok(())
293        }
294    }
295
296    fn set_mem_table(
297        &mut self,
298        contexts: &[VhostUserMemoryRegion],
299        files: Vec<File>,
300    ) -> Result<()> {
301        let (guest_mem, vmm_maps) = VhostUserRegularOps::set_mem_table(contexts, files)?;
302
303        self.handle
304            .set_mem_table(&guest_mem)
305            .map_err(convert_vhost_error)?;
306
307        self.mem = Some(guest_mem);
308        self.vmm_maps = Some(vmm_maps);
309
310        Ok(())
311    }
312
313    fn get_queue_num(&mut self) -> Result<u64> {
314        Ok(NUM_QUEUES as u64)
315    }
316
317    fn set_vring_num(&mut self, index: u32, num: u32) -> Result<()> {
318        if index >= NUM_QUEUES as u32 || num == 0 || num > Queue::MAX_SIZE.into() {
319            return Err(Error::InvalidParam(
320                "set_vring_num: vring index or size out of range",
321            ));
322        }
323
324        let index = index as usize;
325        let num = num as u16;
326
327        let vring = &mut self.vrings[index];
328        if vring.queue.used_ring().0 != 0 {
329            let mem = self.mem.as_ref().ok_or(Error::InvalidParam(
330                "set_vring_num: addresses set but no mem table",
331            ))?;
332            Self::validate_addresses(
333                mem,
334                num,
335                vring.queue.desc_table(),
336                vring.queue.avail_ring(),
337                vring.queue.used_ring(),
338                vring.config.log_addr,
339            )?;
340        }
341
342        vring.queue.set_size(num);
343        Ok(())
344    }
345
346    fn set_vring_addr(
347        &mut self,
348        index: u32,
349        flags: VhostUserVringAddrFlags,
350        descriptor: u64,
351        used: u64,
352        available: u64,
353        log: u64,
354    ) -> Result<()> {
355        if index >= NUM_QUEUES as u32 {
356            return Err(Error::InvalidParam("set_vring_addr: index out of range"));
357        }
358
359        let index = index as usize;
360
361        let mem = self
362            .mem
363            .as_ref()
364            .ok_or(Error::InvalidParam("set_vring_addr: could not get mem"))?;
365        let maps = self.vmm_maps.as_ref().ok_or(Error::InvalidParam(
366            "set_vring_addr: could not get vmm_maps",
367        ))?;
368
369        let desc_gpa = vmm_va_to_gpa(maps, descriptor)?;
370        let avail_gpa = vmm_va_to_gpa(maps, available)?;
371        let used_gpa = vmm_va_to_gpa(maps, used)?;
372        let log_gpa = if flags.contains(VhostUserVringAddrFlags::VHOST_VRING_F_LOG) {
373            vmm_va_to_gpa(maps, log).map(Some)?
374        } else {
375            None
376        };
377
378        let vring = &mut self.vrings[index];
379        let queue_size = vring.queue.size();
380        Self::validate_addresses(mem, queue_size, desc_gpa, avail_gpa, used_gpa, log_gpa)?;
381
382        vring.queue.set_desc_table(desc_gpa);
383        vring.queue.set_avail_ring(avail_gpa);
384        vring.queue.set_used_ring(used_gpa);
385
386        vring.config.flags = flags;
387        vring.config.log_addr = log_gpa;
388
389        Ok(())
390    }
391
392    fn set_vring_base(&mut self, index: u32, base: u32) -> Result<()> {
393        if index >= NUM_QUEUES as u32 {
394            return Err(Error::InvalidParam("set_vring_base: index out of range"));
395        }
396
397        let index = index as usize;
398        let base = base as u16;
399
400        let queue = &mut self.vrings[index].queue;
401        queue.set_next_avail(Wrapping(base));
402        queue.set_next_used(Wrapping(base));
403
404        Ok(())
405    }
406
407    fn get_vring_base(&mut self, index: u32) -> Result<VhostUserVringState> {
408        if index >= NUM_QUEUES as u32 {
409            return Err(Error::InvalidParam("get_vring_base: index out of range"));
410        }
411
412        let index = index as usize;
413        let next_avail = if index == EVENT_QUEUE {
414            self.vrings[index].queue.next_avail().0
415        } else {
416            self.handle
417                .get_vring_base(index)
418                .map_err(convert_vhost_error)?
419        };
420
421        Ok(VhostUserVringState::new(index as u32, next_avail.into()))
422    }
423
424    fn set_vring_kick(&mut self, index: u8, fd: Option<File>) -> Result<()> {
425        if index >= NUM_QUEUES as u8 {
426            return Err(Error::InvalidParam("set_vring_kick: index out of range"));
427        }
428        let file = fd.ok_or(Error::InvalidParam("set_vring_kick: missing fd"))?;
429        let index = usize::from(index);
430        self.vrings[index].config.kick_fd = Some(file);
431        Ok(())
432    }
433
434    fn set_vring_call(&mut self, index: u8, fd: Option<File>) -> Result<()> {
435        if index >= NUM_QUEUES as u8 {
436            return Err(Error::InvalidParam("set_vring_call: index out of range"));
437        }
438        let file = fd.ok_or(Error::InvalidParam("set_vring_call: missing fd"))?;
439        let index = usize::from(index);
440        self.vrings[index].config.call_fd = Some(file);
441        Ok(())
442    }
443
444    fn set_vring_err(&mut self, index: u8, fd: Option<File>) -> Result<()> {
445        if index >= NUM_QUEUES as u8 {
446            return Err(Error::InvalidParam("set_vring_err: index out of range"));
447        }
448        let file = fd.ok_or(Error::InvalidParam("set_vring_err: missing fd"))?;
449        let index = usize::from(index);
450        self.vrings[index].config.err_fd = Some(file);
451        Ok(())
452    }
453
454    fn set_vring_enable(&mut self, index: u32, enable: bool) -> Result<()> {
455        if index >= NUM_QUEUES as u32 {
456            return Err(Error::InvalidParam("vring index out of range"));
457        }
458
459        self.vrings[index as usize].queue.set_ready(enable);
460
461        if index == (EVENT_QUEUE) as u32 {
462            return Ok(());
463        }
464
465        if self.vrings[..EVENT_QUEUE].iter().all(|v| v.queue.ready()) {
466            // All queues are ready.  Start the device.
467            self.program_queues_to_kernel()?;
468            self.handle.set_cid(self.cid).map_err(convert_vhost_error)?;
469            self.handle.start().map_err(convert_vhost_error)
470        } else if !enable {
471            // If we just disabled a vring then stop the device.
472            self.handle.stop().map_err(convert_vhost_error)
473        } else {
474            Ok(())
475        }
476    }
477
478    fn get_config(
479        &mut self,
480        offset: u32,
481        size: u32,
482        _flags: VhostUserConfigFlags,
483    ) -> Result<Vec<u8>> {
484        let start: usize = offset
485            .try_into()
486            .map_err(|_| Error::InvalidParam("offset does not fit in usize"))?;
487        let end: usize = offset
488            .checked_add(size)
489            .and_then(|e| e.try_into().ok())
490            .ok_or(Error::InvalidParam("offset + size does not fit in usize"))?;
491
492        if start >= size_of::<Le64>() || end > size_of::<Le64>() {
493            return Err(Error::InvalidParam(
494                "get_config: offset and/or size out of range",
495            ));
496        }
497
498        Ok(Le64::from(self.cid).as_bytes()[start..end].to_vec())
499    }
500
501    fn set_config(
502        &mut self,
503        _offset: u32,
504        _buf: &[u8],
505        _flags: VhostUserConfigFlags,
506    ) -> Result<()> {
507        Err(Error::InvalidOperation)
508    }
509
510    fn set_backend_req_fd(&mut self, _vu_req: Connection) {
511        // We didn't set VhostUserProtocolFeatures::BACKEND_REQ
512        unreachable!("unexpected set_backend_req_fd");
513    }
514
515    fn get_inflight_fd(
516        &mut self,
517        _inflight: &VhostUserInflight,
518    ) -> Result<(VhostUserInflight, File)> {
519        Err(Error::InvalidOperation)
520    }
521
522    fn set_inflight_fd(&mut self, _inflight: &VhostUserInflight, _file: File) -> Result<()> {
523        Err(Error::InvalidOperation)
524    }
525
526    fn get_max_mem_slots(&mut self) -> Result<u64> {
527        Err(Error::InvalidOperation)
528    }
529
530    fn add_mem_region(&mut self, _region: &VhostUserSingleMemoryRegion, _fd: File) -> Result<()> {
531        Err(Error::InvalidOperation)
532    }
533
534    fn remove_mem_region(&mut self, _region: &VhostUserSingleMemoryRegion) -> Result<()> {
535        Err(Error::InvalidOperation)
536    }
537
538    fn set_device_state_fd(
539        &mut self,
540        _transfer_direction: VhostUserTransferDirection,
541        _migration_phase: VhostUserMigrationPhase,
542        _fd: File,
543    ) -> Result<Option<File>> {
544        Err(Error::InvalidOperation)
545    }
546
547    fn check_device_state(&mut self) -> Result<()> {
548        Err(Error::InvalidOperation)
549    }
550
551    fn get_shmem_config(&mut self) -> Result<Vec<SharedMemoryRegion>> {
552        Ok(Vec::new())
553    }
554}
555
556#[derive(FromArgs)]
557#[argh(subcommand, name = "vsock")]
558/// Vsock device
559pub struct Options {
560    #[argh(option, arg_name = "PATH", hidden_help)]
561    /// deprecated - please use --socket-path instead
562    socket: Option<String>,
563    #[argh(option, arg_name = "PATH")]
564    /// path to the vhost-user socket to bind to.
565    /// If this flag is set, --fd cannot be specified.
566    socket_path: Option<String>,
567    #[argh(option, arg_name = "FD")]
568    /// file descriptor of a connected vhost-user socket.
569    /// If this flag is set, --socket-path cannot be specified.
570    fd: Option<RawDescriptor>,
571
572    #[argh(option, arg_name = "INT")]
573    /// the vsock context id for this device
574    cid: u64,
575    #[argh(
576        option,
577        default = "String::from(\"/dev/vhost-vsock\")",
578        arg_name = "PATH"
579    )]
580    /// path to the vhost-vsock control socket
581    vhost_socket: String,
582}
583
584/// Returns an error if the given `args` is invalid or the device fails to run.
585pub fn run_vsock_device(opts: Options) -> anyhow::Result<()> {
586    let ex = Executor::new().context("failed to create executor")?;
587
588    let conn =
589        BackendConnection::from_opts(opts.socket.as_deref(), opts.socket_path.as_deref(), opts.fd)?;
590
591    let vsock_device = Box::new(VhostUserVsockDevice::new(opts.cid, opts.vhost_socket)?);
592
593    conn.run_device(ex, vsock_device)
594}