mirror of
https://chromium.googlesource.com/crosvm/crosvm
synced 2025-01-28 19:29:20 +00:00
Implement virtio-vsock
Implement the virtual sockets device using vhost subsystem of the host kernel to handle data transfer. BUG=chromium:708267 TEST=build and run maitred in guest VM without issue Change-Id: I35b542c0fc7e0fd9296f7ba3e1dfce60bf524d15 Signed-off-by: Chirantan Ekbote <chirantan@chromium.org> Reviewed-on: https://chromium-review.googlesource.com/638838 Reviewed-by: Stephen Barber <smbarber@chromium.org>
This commit is contained in:
parent
270f7b6a16
commit
88f9cba448
9 changed files with 465 additions and 16 deletions
43
seccomp/x86_64/vhost_vsock_device.policy
Normal file
43
seccomp/x86_64/vhost_vsock_device.policy
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
# Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||||
|
# Use of this source code is governed by a BSD-style license that can be
|
||||||
|
# found in the LICENSE file.
|
||||||
|
|
||||||
|
close: 1
|
||||||
|
exit_group: 1
|
||||||
|
futex: 1
|
||||||
|
# Whitelist vhost_vsock ioctls only.
|
||||||
|
# arg1 == VHOST_GET_FEATURES ||
|
||||||
|
# arg1 == VHOST_SET_FEATURES ||
|
||||||
|
# arg1 == VHOST_SET_OWNER ||
|
||||||
|
# arg1 == VHOST_RESET_OWNER ||
|
||||||
|
# arg1 == VHOST_SET_MEM_TABLE ||
|
||||||
|
# arg1 == VHOST_SET_LOG_BASE ||
|
||||||
|
# arg1 == VHOST_SET_LOG_FD ||
|
||||||
|
# arg1 == VHOST_SET_VRING_NUM ||
|
||||||
|
# arg1 == VHOST_SET_VRING_ADDR ||
|
||||||
|
# arg1 == VHOST_SET_VRING_BASE ||
|
||||||
|
# arg1 == VHOST_GET_VRING_BASE ||
|
||||||
|
# arg1 == VHOST_SET_VRING_KICK ||
|
||||||
|
# arg1 == VHOST_SET_VRING_CALL ||
|
||||||
|
# arg1 == VHOST_SET_VRING_ERR ||
|
||||||
|
# arg1 == VHOST_VSOCK_SET_GUEST_CID ||
|
||||||
|
# arg1 == VHOST_VSOCK_SET_RUNNING
|
||||||
|
ioctl: arg1 == 0x8008af00 || arg1 == 0x4008af00 || arg1 == 0x0000af01 || arg1 == 0x0000af02 || arg1 == 0x4008af03 || arg1 == 0x4008af04 || arg1 == 0x4004af07 || arg1 == 0x4008af10 || arg1 == 0x4028af11 || arg1 == 0x4008af12 || arg1 == 0xc008af12 || arg1 == 0x4008af20 || arg1 == 0x4008af21 || arg1 == 0x4008af22 || arg1 == 0x4008af60 || arg1 == 0x4004af61
|
||||||
|
# Disallow mmap with PROT_EXEC set. The syntax here doesn't allow bit
|
||||||
|
# negation, thus the manually negated mask constant.
|
||||||
|
mmap: arg2 in 0xfffffffb
|
||||||
|
mprotect: arg2 in 0xfffffffb
|
||||||
|
munmap: 1
|
||||||
|
poll: 1
|
||||||
|
read: 1
|
||||||
|
connect: 1
|
||||||
|
sendto: 1
|
||||||
|
recvfrom: 1
|
||||||
|
sched_getaffinity: 1
|
||||||
|
set_robust_list: 1
|
||||||
|
sigaltstack: 1
|
||||||
|
# Disallow clone's other than new threads.
|
||||||
|
# arg0 is flags. Because kernel.
|
||||||
|
clone: arg0 & 0x00010000
|
||||||
|
write: 1
|
||||||
|
getpid: 1
|
|
@ -30,6 +30,7 @@ const DEVICE_FAILED: u32 = 0x80;
|
||||||
const TYPE_NET: u32 = 1;
|
const TYPE_NET: u32 = 1;
|
||||||
const TYPE_BLOCK: u32 = 2;
|
const TYPE_BLOCK: u32 = 2;
|
||||||
const TYPE_RNG: u32 = 4;
|
const TYPE_RNG: u32 = 4;
|
||||||
|
const TYPE_VSOCK: u32 = 19;
|
||||||
const TYPE_WL: u32 = 30;
|
const TYPE_WL: u32 = 30;
|
||||||
|
|
||||||
const INTERRUPT_STATUS_USED_RING: u32 = 0x1;
|
const INTERRUPT_STATUS_USED_RING: u32 = 0x1;
|
||||||
|
|
|
@ -11,9 +11,11 @@ use sys_util::Error as SysError;
|
||||||
use vhost::Error as VhostError;
|
use vhost::Error as VhostError;
|
||||||
|
|
||||||
mod net;
|
mod net;
|
||||||
|
mod vsock;
|
||||||
mod worker;
|
mod worker;
|
||||||
|
|
||||||
pub use self::net::Net;
|
pub use self::net::Net;
|
||||||
|
pub use self::vsock::Vsock;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
|
@ -57,6 +59,10 @@ pub enum Error {
|
||||||
VhostSetVringKick(VhostError),
|
VhostSetVringKick(VhostError),
|
||||||
/// Net set backend failed.
|
/// Net set backend failed.
|
||||||
VhostNetSetBackend(VhostError),
|
VhostNetSetBackend(VhostError),
|
||||||
|
/// Failed to set CID for guest.
|
||||||
|
VhostVsockSetCid(VhostError),
|
||||||
|
/// Failed to start vhost-vsock driver.
|
||||||
|
VhostVsockStart(VhostError),
|
||||||
/// Failed to create vhost eventfd.
|
/// Failed to create vhost eventfd.
|
||||||
VhostIrqCreate(SysError),
|
VhostIrqCreate(SysError),
|
||||||
/// Failed to read vhost eventfd.
|
/// Failed to read vhost eventfd.
|
||||||
|
|
280
src/hw/virtio/vhost/vsock.rs
Normal file
280
src/hw/virtio/vhost/vsock.rs
Normal file
|
@ -0,0 +1,280 @@
|
||||||
|
// Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
use std::os::unix::io::{AsRawFd, RawFd};
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::sync::atomic::AtomicUsize;
|
||||||
|
use std::thread::spawn;
|
||||||
|
|
||||||
|
use byteorder::{ByteOrder, LittleEndian};
|
||||||
|
|
||||||
|
use sys_util::{EventFd, GuestMemory};
|
||||||
|
use vhost::Vsock as VhostVsockHandle;
|
||||||
|
use virtio_sys::vhost;
|
||||||
|
|
||||||
|
use super::{Error, Result};
|
||||||
|
use super::super::{Queue, VirtioDevice, TYPE_VSOCK};
|
||||||
|
use super::worker::Worker;
|
||||||
|
|
||||||
|
const QUEUE_SIZE: u16 = 256;
|
||||||
|
const NUM_QUEUES: usize = 3;
|
||||||
|
const QUEUE_SIZES: &'static [u16] = &[QUEUE_SIZE; NUM_QUEUES];
|
||||||
|
|
||||||
|
pub struct Vsock {
|
||||||
|
worker_kill_evt: Option<EventFd>,
|
||||||
|
kill_evt: Option<EventFd>,
|
||||||
|
vhost_handle: Option<VhostVsockHandle>,
|
||||||
|
cid: u64,
|
||||||
|
interrupt: Option<EventFd>,
|
||||||
|
avail_features: u64,
|
||||||
|
acked_features: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Vsock {
|
||||||
|
/// Create a new virtio-vsock device with the given VM cid.
|
||||||
|
pub fn new(cid: u64, mem: &GuestMemory) -> Result<Vsock> {
|
||||||
|
let kill_evt = EventFd::new().map_err(Error::CreateKillEventFd)?;
|
||||||
|
let handle = VhostVsockHandle::new(mem).map_err(Error::VhostOpen)?;
|
||||||
|
|
||||||
|
let avail_features =
|
||||||
|
1 << vhost::VIRTIO_F_NOTIFY_ON_EMPTY | 1 << vhost::VIRTIO_RING_F_INDIRECT_DESC |
|
||||||
|
1 << vhost::VIRTIO_RING_F_EVENT_IDX | 1 << vhost::VHOST_F_LOG_ALL |
|
||||||
|
1 << vhost::VIRTIO_F_ANY_LAYOUT | 1 << vhost::VIRTIO_F_VERSION_1;
|
||||||
|
|
||||||
|
Ok(Vsock {
|
||||||
|
worker_kill_evt: Some(kill_evt.try_clone().map_err(Error::CloneKillEventFd)?),
|
||||||
|
kill_evt: Some(kill_evt),
|
||||||
|
vhost_handle: Some(handle),
|
||||||
|
cid: cid,
|
||||||
|
interrupt: Some(EventFd::new().map_err(Error::VhostIrqCreate)?),
|
||||||
|
avail_features: avail_features,
|
||||||
|
acked_features: 0,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_for_testing(cid: u64, features: u64) -> Vsock {
|
||||||
|
Vsock {
|
||||||
|
worker_kill_evt: None,
|
||||||
|
kill_evt: None,
|
||||||
|
vhost_handle: None,
|
||||||
|
cid: cid,
|
||||||
|
interrupt: None,
|
||||||
|
avail_features: features,
|
||||||
|
acked_features: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn acked_features(&self) -> u64 {
|
||||||
|
self.acked_features
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for Vsock {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
// Only kill the child if it claimed its eventfd.
|
||||||
|
if self.worker_kill_evt.is_none() {
|
||||||
|
if let Some(ref kill_evt) = self.kill_evt {
|
||||||
|
// Ignore the result because there is nothing we can do about it.
|
||||||
|
let _ = kill_evt.write(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VirtioDevice for Vsock {
|
||||||
|
fn keep_fds(&self) -> Vec<RawFd> {
|
||||||
|
let mut keep_fds = Vec::new();
|
||||||
|
|
||||||
|
if let Some(ref handle) = self.vhost_handle {
|
||||||
|
keep_fds.push(handle.as_raw_fd());
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(ref interrupt) = self.interrupt {
|
||||||
|
keep_fds.push(interrupt.as_raw_fd());
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(ref worker_kill_evt) = self.worker_kill_evt {
|
||||||
|
keep_fds.push(worker_kill_evt.as_raw_fd());
|
||||||
|
}
|
||||||
|
|
||||||
|
keep_fds
|
||||||
|
}
|
||||||
|
|
||||||
|
fn device_type(&self) -> u32 {
|
||||||
|
TYPE_VSOCK
|
||||||
|
}
|
||||||
|
|
||||||
|
fn queue_max_sizes(&self) -> &[u16] {
|
||||||
|
QUEUE_SIZES
|
||||||
|
}
|
||||||
|
|
||||||
|
fn features(&self, page: u32) -> u32 {
|
||||||
|
match page {
|
||||||
|
// Get the lower 32-bits of the features bitfield.
|
||||||
|
0 => self.avail_features as u32,
|
||||||
|
// Get the upper 32-bits of the features bitfield.
|
||||||
|
1 => (self.avail_features >> 32) as u32,
|
||||||
|
_ => {
|
||||||
|
warn!(
|
||||||
|
"vsock: virtio-vsock got request for features page: {}",
|
||||||
|
page
|
||||||
|
);
|
||||||
|
0u32
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_config(&self, offset: u64, data: &mut [u8]) {
|
||||||
|
match offset {
|
||||||
|
0 if data.len() == 8 => LittleEndian::write_u64(data, self.cid),
|
||||||
|
0 if data.len() == 4 => LittleEndian::write_u32(data, (self.cid & 0xffffffff) as u32),
|
||||||
|
4 if data.len() == 4 => {
|
||||||
|
LittleEndian::write_u32(data, ((self.cid >> 32) & 0xffffffff) as u32)
|
||||||
|
},
|
||||||
|
_ => warn!(
|
||||||
|
"vsock: virtio-vsock received invalid read request of {} bytes at offset {}",
|
||||||
|
data.len(),
|
||||||
|
offset
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ack_features(&mut self, page: u32, value: u32) {
|
||||||
|
let mut v = match page {
|
||||||
|
0 => value as u64,
|
||||||
|
1 => (value as u64) << 32,
|
||||||
|
_ => {
|
||||||
|
warn!(
|
||||||
|
"vsock: virtio-vsock device cannot ack unknown feature page: {}",
|
||||||
|
page
|
||||||
|
);
|
||||||
|
0u64
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Check if the guest is ACK'ing a feature that we didn't claim to have.
|
||||||
|
let unrequested_features = v & !self.avail_features;
|
||||||
|
if unrequested_features != 0 {
|
||||||
|
warn!("vsock: virtio-vsock got unknown feature ack: {:x}", v);
|
||||||
|
|
||||||
|
// Don't count these features as acked.
|
||||||
|
v &= !unrequested_features;
|
||||||
|
}
|
||||||
|
self.acked_features |= v;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn activate(
|
||||||
|
&mut self,
|
||||||
|
_: GuestMemory,
|
||||||
|
interrupt_evt: EventFd,
|
||||||
|
status: Arc<AtomicUsize>,
|
||||||
|
queues: Vec<Queue>,
|
||||||
|
queue_evts: Vec<EventFd>,
|
||||||
|
) {
|
||||||
|
if queues.len() != NUM_QUEUES || queue_evts.len() != NUM_QUEUES {
|
||||||
|
error!("net: expected {} queues, got {}", NUM_QUEUES, queues.len());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(vhost_handle) = self.vhost_handle.take() {
|
||||||
|
if let Some(interrupt) = self.interrupt.take() {
|
||||||
|
if let Some(kill_evt) = self.worker_kill_evt.take() {
|
||||||
|
let acked_features = self.acked_features;
|
||||||
|
let cid = self.cid;
|
||||||
|
spawn(move || {
|
||||||
|
// The third vq is an event-only vq that is not handled by the vhost
|
||||||
|
// subsystem (but still needs to exist). Split it off here.
|
||||||
|
let vhost_queues = queues[..2].to_vec();
|
||||||
|
let mut worker = Worker::new(
|
||||||
|
vhost_queues,
|
||||||
|
vhost_handle,
|
||||||
|
interrupt,
|
||||||
|
status,
|
||||||
|
interrupt_evt,
|
||||||
|
acked_features,
|
||||||
|
);
|
||||||
|
let activate_vqs = |handle: &VhostVsockHandle| -> Result<()> {
|
||||||
|
handle.set_cid(cid).map_err(Error::VhostVsockSetCid)?;
|
||||||
|
handle.start().map_err(Error::VhostVsockStart)?;
|
||||||
|
Ok(())
|
||||||
|
};
|
||||||
|
let result = worker.run(queue_evts, QUEUE_SIZES, kill_evt, activate_vqs);
|
||||||
|
if let Err(e) = result {
|
||||||
|
error!("vsock worker thread exited with error: {:?}", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
use byteorder::{ByteOrder, LittleEndian};
|
||||||
|
#[test]
|
||||||
|
fn ack_features() {
|
||||||
|
let cid = 5;
|
||||||
|
let features: u64 = (1 << 20) | (1 << 49) | (1 << 2) | (1 << 19);
|
||||||
|
let mut acked_features: u64 = 0;
|
||||||
|
let mut unavailable_features: u64 = 0;
|
||||||
|
|
||||||
|
let mut vsock = Vsock::new_for_testing(cid, features);
|
||||||
|
assert_eq!(acked_features, vsock.acked_features());
|
||||||
|
|
||||||
|
acked_features |= 1 << 2;
|
||||||
|
vsock.ack_features(0, (acked_features & 0xffffffff) as u32);
|
||||||
|
assert_eq!(acked_features, vsock.acked_features());
|
||||||
|
|
||||||
|
acked_features |= 1 << 49;
|
||||||
|
vsock.ack_features(1, (acked_features >> 32) as u32);
|
||||||
|
assert_eq!(acked_features, vsock.acked_features());
|
||||||
|
|
||||||
|
acked_features |= 1 << 60;
|
||||||
|
unavailable_features |= 1 << 60;
|
||||||
|
vsock.ack_features(1, (acked_features >> 32) as u32);
|
||||||
|
assert_eq!(acked_features & !unavailable_features, vsock.acked_features());
|
||||||
|
|
||||||
|
acked_features |= 1 << 1;
|
||||||
|
unavailable_features |= 1 << 1;
|
||||||
|
vsock.ack_features(0, (acked_features & 0xffffffff) as u32);
|
||||||
|
assert_eq!(acked_features & !unavailable_features, vsock.acked_features());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn read_config() {
|
||||||
|
let cid = 0xfca9a559fdcb9756;
|
||||||
|
let vsock = Vsock::new_for_testing(cid, 0);
|
||||||
|
|
||||||
|
let mut buf = [0 as u8; 8];
|
||||||
|
vsock.read_config(0, &mut buf);
|
||||||
|
assert_eq!(cid, LittleEndian::read_u64(&buf));
|
||||||
|
|
||||||
|
vsock.read_config(0, &mut buf[..4]);
|
||||||
|
assert_eq!((cid & 0xffffffff) as u32, LittleEndian::read_u32(&buf[..4]));
|
||||||
|
|
||||||
|
vsock.read_config(4, &mut buf[..4]);
|
||||||
|
assert_eq!((cid >> 32) as u32, LittleEndian::read_u32(&buf[..4]));
|
||||||
|
|
||||||
|
let data: [u8; 8] = [8, 226, 5, 46, 159, 59, 89, 77];
|
||||||
|
buf.copy_from_slice(&data);
|
||||||
|
|
||||||
|
vsock.read_config(12, &mut buf);
|
||||||
|
assert_eq!(&buf, &data);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn features() {
|
||||||
|
let cid = 5;
|
||||||
|
let features: u64 = 0xfc195ae8db88cff9;
|
||||||
|
|
||||||
|
let vsock = Vsock::new_for_testing(cid, features);
|
||||||
|
assert_eq!((features & 0xffffffff) as u32, vsock.features(0));
|
||||||
|
assert_eq!((features >> 32) as u32, vsock.features(1));
|
||||||
|
assert_eq!(0, vsock.features(559));
|
||||||
|
assert_eq!(0, vsock.features(3));
|
||||||
|
}
|
||||||
|
}
|
39
src/main.rs
39
src/main.rs
|
@ -60,11 +60,14 @@ enum Error {
|
||||||
VhostNetDeviceNew(hw::virtio::vhost::Error),
|
VhostNetDeviceNew(hw::virtio::vhost::Error),
|
||||||
NetDeviceNew(hw::virtio::NetError),
|
NetDeviceNew(hw::virtio::NetError),
|
||||||
NetDeviceRootSetup(sys_util::Error),
|
NetDeviceRootSetup(sys_util::Error),
|
||||||
|
VhostVsockDeviceNew(hw::virtio::vhost::Error),
|
||||||
|
VsockDeviceRootSetup(sys_util::Error),
|
||||||
DeviceJail(io_jail::Error),
|
DeviceJail(io_jail::Error),
|
||||||
DevicePivotRoot(io_jail::Error),
|
DevicePivotRoot(io_jail::Error),
|
||||||
RegisterBlock(device_manager::Error),
|
RegisterBlock(device_manager::Error),
|
||||||
RegisterNet(device_manager::Error),
|
RegisterNet(device_manager::Error),
|
||||||
RegisterWayland(device_manager::Error),
|
RegisterWayland(device_manager::Error),
|
||||||
|
RegisterVsock(device_manager::Error),
|
||||||
Cmdline(kernel_cmdline::Error),
|
Cmdline(kernel_cmdline::Error),
|
||||||
MissingWayland(PathBuf),
|
MissingWayland(PathBuf),
|
||||||
RegisterIrqfd(sys_util::Error),
|
RegisterIrqfd(sys_util::Error),
|
||||||
|
@ -114,12 +117,17 @@ impl fmt::Display for Error {
|
||||||
}
|
}
|
||||||
&Error::RegisterBlock(ref e) => write!(f, "error registering block device: {:?}", e),
|
&Error::RegisterBlock(ref e) => write!(f, "error registering block device: {:?}", e),
|
||||||
&Error::VhostNetDeviceNew(ref e) => write!(f, "failed to set up vhost networking: {:?}", e),
|
&Error::VhostNetDeviceNew(ref e) => write!(f, "failed to set up vhost networking: {:?}", e),
|
||||||
|
&Error::RegisterVsock(ref e) => write!(f, "error registering virtual socket device: {:?}", e),
|
||||||
&Error::NetDeviceNew(ref e) => write!(f, "failed to set up virtio networking: {:?}", e),
|
&Error::NetDeviceNew(ref e) => write!(f, "failed to set up virtio networking: {:?}", e),
|
||||||
&Error::NetDeviceRootSetup(ref e) => {
|
&Error::NetDeviceRootSetup(ref e) => {
|
||||||
write!(f, "failed to create root directory for a net device: {:?}", e)
|
write!(f, "failed to create root directory for a net device: {:?}", e)
|
||||||
}
|
}
|
||||||
&Error::DeviceJail(ref e) => write!(f, "failed to jail device: {}", e),
|
&Error::DeviceJail(ref e) => write!(f, "failed to jail device: {}", e),
|
||||||
&Error::DevicePivotRoot(ref e) => write!(f, "failed to pivot root device: {}", e),
|
&Error::DevicePivotRoot(ref e) => write!(f, "failed to pivot root device: {}", e),
|
||||||
|
&Error::VhostVsockDeviceNew(ref e) => write!(f, "failed to set up virtual socket device: {:?}", e),
|
||||||
|
&Error::VsockDeviceRootSetup(ref e) => {
|
||||||
|
write!(f, "failed to create root directory for a vsock device: {:?}", e)
|
||||||
|
}
|
||||||
&Error::RegisterNet(ref e) => write!(f, "error registering net device: {:?}", e),
|
&Error::RegisterNet(ref e) => write!(f, "error registering net device: {:?}", e),
|
||||||
&Error::RegisterRng(ref e) => write!(f, "error registering rng device: {:?}", e),
|
&Error::RegisterRng(ref e) => write!(f, "error registering rng device: {:?}", e),
|
||||||
&Error::RngDeviceNew(ref e) => write!(f, "failed to set up rng: {:?}", e),
|
&Error::RngDeviceNew(ref e) => write!(f, "failed to set up rng: {:?}", e),
|
||||||
|
@ -190,6 +198,7 @@ struct Config {
|
||||||
socket_path: Option<PathBuf>,
|
socket_path: Option<PathBuf>,
|
||||||
multiprocess: bool,
|
multiprocess: bool,
|
||||||
warn_unknown_ports: bool,
|
warn_unknown_ports: bool,
|
||||||
|
cid: Option<u64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
const KERNEL_START_OFFSET: usize = 0x200000;
|
const KERNEL_START_OFFSET: usize = 0x200000;
|
||||||
|
@ -383,6 +392,24 @@ fn run_config(cfg: Config) -> Result<()> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let vsock_root = TempDir::new(&PathBuf::from("/tmp/vsock_root"))
|
||||||
|
.map_err(Error::VsockDeviceRootSetup)?;
|
||||||
|
if let Some(cid) = cfg.cid {
|
||||||
|
let vsock_box = Box::new(hw::virtio::vhost::Vsock::new(cid, &guest_mem)
|
||||||
|
.map_err(|e| Error::VhostVsockDeviceNew(e))?);
|
||||||
|
|
||||||
|
let jail = if cfg.multiprocess {
|
||||||
|
let root_path = vsock_root.as_path().unwrap();
|
||||||
|
let policy_path = Path::new("vhost_vsock_device.policy");
|
||||||
|
|
||||||
|
Some(create_base_minijail(root_path, policy_path)?)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
device_manager.register_mmio(vsock_box, jail, &mut cmdline).map_err(Error::RegisterVsock)?;
|
||||||
|
}
|
||||||
|
|
||||||
if !cfg.params.is_empty() {
|
if !cfg.params.is_empty() {
|
||||||
cmdline
|
cmdline
|
||||||
.insert_str(cfg.params)
|
.insert_str(cfg.params)
|
||||||
|
@ -856,6 +883,17 @@ fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument::
|
||||||
"multiprocess" => {
|
"multiprocess" => {
|
||||||
cfg.multiprocess = true;
|
cfg.multiprocess = true;
|
||||||
}
|
}
|
||||||
|
"cid" => {
|
||||||
|
if cfg.cid.is_some() {
|
||||||
|
return Err(argument::Error::TooManyArguments("`cid` alread given".to_owned()));
|
||||||
|
}
|
||||||
|
cfg.cid = Some(value.unwrap().parse().map_err(|_| {
|
||||||
|
argument::Error::InvalidValue {
|
||||||
|
value: value.unwrap().to_owned(),
|
||||||
|
expected: "this value for `cid` must be an unsigned integer",
|
||||||
|
}
|
||||||
|
})?);
|
||||||
|
}
|
||||||
"help" => return Err(argument::Error::PrintHelp),
|
"help" => return Err(argument::Error::PrintHelp),
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
|
@ -892,6 +930,7 @@ fn run_vm(args: std::env::Args) {
|
||||||
"PATH",
|
"PATH",
|
||||||
"Path to put the control socket. If PATH is a directory, a name will be generated."),
|
"Path to put the control socket. If PATH is a directory, a name will be generated."),
|
||||||
Argument::short_flag('u', "multiprocess", "Run each device in a child process."),
|
Argument::short_flag('u', "multiprocess", "Run each device in a child process."),
|
||||||
|
Argument::value("cid", "CID", "Context ID for virtual sockets"),
|
||||||
Argument::short_flag('h', "help", "Print help message.")];
|
Argument::short_flag('h', "help", "Print help message.")];
|
||||||
|
|
||||||
let mut cfg = Config::default();
|
let mut cfg = Config::default();
|
||||||
|
|
|
@ -8,7 +8,10 @@ extern crate sys_util;
|
||||||
extern crate virtio_sys;
|
extern crate virtio_sys;
|
||||||
|
|
||||||
pub mod net;
|
pub mod net;
|
||||||
|
mod vsock;
|
||||||
|
|
||||||
pub use net::Net;
|
pub use net::Net;
|
||||||
|
pub use vsock::Vsock;
|
||||||
|
|
||||||
use std::io::Error as IoError;
|
use std::io::Error as IoError;
|
||||||
use std::mem;
|
use std::mem;
|
||||||
|
|
|
@ -4,10 +4,9 @@
|
||||||
|
|
||||||
use libc;
|
use libc;
|
||||||
use net_util;
|
use net_util;
|
||||||
use std::ffi::CString;
|
use std::fs::{File, OpenOptions};
|
||||||
use std::fs::File;
|
use std::os::unix::fs::OpenOptionsExt;
|
||||||
use std::io::Error as IoError;
|
use std::os::unix::io::{AsRawFd, RawFd};
|
||||||
use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
|
|
||||||
use virtio_sys;
|
use virtio_sys;
|
||||||
|
|
||||||
use sys_util::{ioctl_with_ref, GuestMemory};
|
use sys_util::{ioctl_with_ref, GuestMemory};
|
||||||
|
@ -43,19 +42,13 @@ impl Net {
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
/// * `mem` - Guest memory mapping.
|
/// * `mem` - Guest memory mapping.
|
||||||
pub fn new(mem: &GuestMemory) -> Result<Net> {
|
pub fn new(mem: &GuestMemory) -> Result<Net> {
|
||||||
// Open calls are safe because we give a constant nul-terminated
|
|
||||||
// string and verify the result. The CString unwrap is safe because
|
|
||||||
// DEVICE does not have any embedded '\0' characters.
|
|
||||||
let fd = unsafe {
|
|
||||||
libc::open(CString::new(DEVICE).unwrap().as_ptr(),
|
|
||||||
libc::O_RDWR | libc::O_NONBLOCK | libc::O_CLOEXEC)
|
|
||||||
};
|
|
||||||
if fd < 0 {
|
|
||||||
return Err(Error::VhostOpen(IoError::last_os_error()));
|
|
||||||
}
|
|
||||||
Ok(Net {
|
Ok(Net {
|
||||||
// There are no other users of this fd, so this is safe.
|
fd: OpenOptions::new()
|
||||||
fd: unsafe { File::from_raw_fd(fd) },
|
.read(true)
|
||||||
|
.write(true)
|
||||||
|
.custom_flags(libc::O_CLOEXEC | libc::O_NONBLOCK)
|
||||||
|
.open(DEVICE)
|
||||||
|
.map_err(Error::VhostOpen)?,
|
||||||
mem: mem.clone(),
|
mem: mem.clone(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
82
vhost/src/vsock.rs
Normal file
82
vhost/src/vsock.rs
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
// Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
use libc;
|
||||||
|
use std::fs::{File, OpenOptions};
|
||||||
|
use std::os::unix::fs::OpenOptionsExt;
|
||||||
|
use std::os::unix::io::{AsRawFd, RawFd};
|
||||||
|
|
||||||
|
use sys_util::{ioctl_with_ref, GuestMemory};
|
||||||
|
use virtio_sys::{VHOST_VSOCK_SET_GUEST_CID, VHOST_VSOCK_SET_RUNNING};
|
||||||
|
|
||||||
|
use super::{ioctl_result, Error, Result, Vhost};
|
||||||
|
|
||||||
|
static DEVICE: &'static str = "/dev/vhost-vsock";
|
||||||
|
|
||||||
|
/// Handle for running VHOST_VSOCK ioctls.
|
||||||
|
pub struct Vsock {
|
||||||
|
fd: File,
|
||||||
|
mem: GuestMemory,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Vsock {
|
||||||
|
/// Open a handle to a new VHOST_VSOCK instance.
|
||||||
|
pub fn new(mem: &GuestMemory) -> Result<Vsock> {
|
||||||
|
Ok(Vsock {
|
||||||
|
fd: OpenOptions::new()
|
||||||
|
.read(true)
|
||||||
|
.write(true)
|
||||||
|
.custom_flags(libc::O_CLOEXEC | libc::O_NONBLOCK)
|
||||||
|
.open(DEVICE)
|
||||||
|
.map_err(Error::VhostOpen)?,
|
||||||
|
mem: mem.clone(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the CID for the guest. This number is used for routing all data destined for
|
||||||
|
/// programs
|
||||||
|
/// running in the guest.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `cid` - CID to assign to the guest
|
||||||
|
pub fn set_cid(&self, cid: u64) -> Result<()> {
|
||||||
|
let ret = unsafe { ioctl_with_ref(&self.fd, VHOST_VSOCK_SET_GUEST_CID(), &cid) };
|
||||||
|
if ret < 0 {
|
||||||
|
return ioctl_result();
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tell the VHOST driver to start performing data transfer.
|
||||||
|
pub fn start(&self) -> Result<()> {
|
||||||
|
self.set_running(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tell the VHOST driver to stop performing data transfer.
|
||||||
|
pub fn stop(&self) -> Result<()> {
|
||||||
|
self.set_running(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_running(&self, running: bool) -> Result<()> {
|
||||||
|
let on: ::std::os::raw::c_int = if running { 1 } else { 0 };
|
||||||
|
let ret = unsafe { ioctl_with_ref(&self.fd, VHOST_VSOCK_SET_RUNNING(), &on) };
|
||||||
|
|
||||||
|
if ret < 0 {
|
||||||
|
return ioctl_result();
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Vhost for Vsock {
|
||||||
|
fn mem(&self) -> &GuestMemory {
|
||||||
|
&self.mem
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRawFd for Vsock {
|
||||||
|
fn as_raw_fd(&self) -> RawFd {
|
||||||
|
self.fd.as_raw_fd()
|
||||||
|
}
|
||||||
|
}
|
|
@ -41,3 +41,5 @@ ioctl_iow_nr!(VHOST_SCSI_CLEAR_ENDPOINT, VHOST, 0x41, vhost_scsi_target);
|
||||||
ioctl_iow_nr!(VHOST_SCSI_GET_ABI_VERSION, VHOST, 0x42, ::std::os::raw::c_int);
|
ioctl_iow_nr!(VHOST_SCSI_GET_ABI_VERSION, VHOST, 0x42, ::std::os::raw::c_int);
|
||||||
ioctl_iow_nr!(VHOST_SCSI_SET_EVENTS_MISSED, VHOST, 0x43, ::std::os::raw::c_uint);
|
ioctl_iow_nr!(VHOST_SCSI_SET_EVENTS_MISSED, VHOST, 0x43, ::std::os::raw::c_uint);
|
||||||
ioctl_iow_nr!(VHOST_SCSI_GET_EVENTS_MISSED, VHOST, 0x44, ::std::os::raw::c_uint);
|
ioctl_iow_nr!(VHOST_SCSI_GET_EVENTS_MISSED, VHOST, 0x44, ::std::os::raw::c_uint);
|
||||||
|
ioctl_iow_nr!(VHOST_VSOCK_SET_GUEST_CID, VHOST, 0x60, ::std::os::raw::c_ulonglong);
|
||||||
|
ioctl_iow_nr!(VHOST_VSOCK_SET_RUNNING, VHOST, 0x61, ::std::os::raw::c_int);
|
||||||
|
|
Loading…
Reference in a new issue