device_virtio_vsock/sys/
linux.rs

1// Copyright 2023 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::path::Path;
6use std::path::PathBuf;
7
8use devices::virtio::VirtioDevice;
9use devices::VirtioDeviceArgs;
10use devices::VirtioDeviceModule;
11use serde::Deserialize;
12use serde::Serialize;
13use serde_keyvalue::FromKeyValues;
14
15static VHOST_VSOCK_DEFAULT_PATH: &str = "/dev/vhost-vsock";
16
17fn default_vsock_path() -> PathBuf {
18    PathBuf::from(VHOST_VSOCK_DEFAULT_PATH)
19}
20
21#[derive(Debug, Deserialize, Serialize, PartialEq, Eq, FromKeyValues)]
22#[serde(deny_unknown_fields, rename_all = "kebab-case")]
23/// Configuration for a Vsock device.
24pub struct VsockConfig {
25    /// CID to be used for this vsock device.
26    pub cid: u64,
27    /// Path to the vhost-vsock device.
28    #[serde(default = "default_vsock_path", rename = "device")]
29    pub vhost_device: PathBuf,
30    #[serde(default)]
31    pub max_queue_sizes: Option<[u16; 3]>,
32}
33
34impl VsockConfig {
35    /// Create a new vsock configuration. If `vhost_device` is `None`, the default vhost-vsock
36    /// device path will be used.
37    pub fn new<P: AsRef<Path>>(cid: u64, vhost_device: Option<P>) -> Self {
38        Self {
39            cid,
40            #[cfg(any(target_os = "android", target_os = "linux"))]
41            vhost_device: vhost_device
42                .map(|p| PathBuf::from(p.as_ref()))
43                .unwrap_or_else(|| PathBuf::from(VHOST_VSOCK_DEFAULT_PATH)),
44            max_queue_sizes: None,
45        }
46    }
47}
48
49#[derive(Serialize, Deserialize)]
50pub struct VirtioVsockModule {
51    config: VsockConfig,
52}
53
54impl VirtioVsockModule {
55    pub fn new(config: VsockConfig) -> Self {
56        Self { config }
57    }
58}
59
60impl VirtioDeviceModule for VirtioVsockModule {
61    fn sort_name(&self) -> &'static str {
62        "vsock"
63    }
64
65    fn create(&self, args: &mut VirtioDeviceArgs<'_>) -> anyhow::Result<Box<dyn VirtioDevice>> {
66        let features = devices::virtio::base_features(args.protection_type);
67        let dev = crate::vhost::Vsock::new(features, &self.config)?;
68        Ok(Box::new(dev))
69    }
70
71    fn create_jail(
72        &self,
73        jail_config: &jail::JailConfig,
74    ) -> anyhow::Result<Option<minijail::Minijail>> {
75        let jail = jail::simple_jail(
76            Some(jail_config),
77            &devices::virtio::VirtioDeviceType::Regular.seccomp_policy_file("vhost_vsock"),
78        )?;
79        Ok(jail)
80    }
81}
82
83#[cfg(test)]
84mod tests {
85    use serde_keyvalue::from_key_values;
86    use serde_keyvalue::ErrorKind;
87    use serde_keyvalue::ParseError;
88
89    use super::*;
90
91    fn from_vsock_arg(options: &str) -> Result<VsockConfig, ParseError> {
92        from_key_values(options)
93    }
94
95    #[test]
96    fn params_from_key_values() {
97        // Default device
98        assert_eq!(
99            from_vsock_arg("cid=56").unwrap(),
100            VsockConfig {
101                vhost_device: VHOST_VSOCK_DEFAULT_PATH.into(),
102                cid: 56,
103                max_queue_sizes: None,
104            }
105        );
106
107        // No argument
108        assert_eq!(
109            from_vsock_arg("").unwrap_err(),
110            ParseError {
111                kind: ErrorKind::SerdeError("missing field `cid`".into()),
112                pos: 0
113            }
114        );
115
116        // CID passed without key
117        assert_eq!(
118            from_vsock_arg("78").unwrap(),
119            VsockConfig {
120                #[cfg(any(target_os = "android", target_os = "linux"))]
121                vhost_device: VHOST_VSOCK_DEFAULT_PATH.into(),
122                cid: 78,
123                max_queue_sizes: None,
124            }
125        );
126
127        // CID passed twice
128        assert_eq!(
129            from_vsock_arg("cid=42,cid=56").unwrap_err(),
130            ParseError {
131                kind: ErrorKind::SerdeError("duplicate field `cid`".into()),
132                pos: 0,
133            }
134        );
135
136        // Invalid argument
137        assert_eq!(
138            from_vsock_arg("invalid=foo").unwrap_err(),
139            ParseError {
140                kind: ErrorKind::SerdeError(
141                    "unknown field `invalid`, expected one of `cid`, `device`, `max-queue-sizes`"
142                        .into()
143                ),
144                pos: 0,
145            }
146        );
147
148        // Path device
149        assert_eq!(
150            from_vsock_arg("device=/some/path,cid=56").unwrap(),
151            VsockConfig {
152                vhost_device: "/some/path".into(),
153                cid: 56,
154                max_queue_sizes: None,
155            }
156        );
157
158        // CID passed without key
159        assert_eq!(
160            from_vsock_arg("56,device=/some/path").unwrap(),
161            VsockConfig {
162                vhost_device: "/some/path".into(),
163                cid: 56,
164                max_queue_sizes: None,
165            }
166        );
167
168        // Missing cid
169        assert_eq!(
170            from_vsock_arg("device=42").unwrap_err(),
171            ParseError {
172                kind: ErrorKind::SerdeError("missing field `cid`".into()),
173                pos: 0,
174            }
175        );
176
177        // Device passed twice
178        assert_eq!(
179            from_vsock_arg("cid=56,device=42,device=/some/path").unwrap_err(),
180            ParseError {
181                kind: ErrorKind::SerdeError("duplicate field `device`".into()),
182                pos: 0,
183            }
184        );
185
186        // Queue sizes
187        assert_eq!(
188            from_vsock_arg("cid=56,max-queue-sizes=[1,2,4]").unwrap(),
189            VsockConfig {
190                vhost_device: VHOST_VSOCK_DEFAULT_PATH.into(),
191                cid: 56,
192                max_queue_sizes: Some([1, 2, 4]),
193            }
194        );
195    }
196}