vhost: add crate for interacting with vhost net

Signed-off-by: Stephen Barber <smbarber@chromium.org>

BUG=chromium:738639
TEST=cargo test

Change-Id: I83ef1657a90c3946db296c2e743397dbd1947de4
Reviewed-on: https://chromium-review.googlesource.com/538101
Commit-Ready: Stephen Barber <smbarber@chromium.org>
Tested-by: Stephen Barber <smbarber@chromium.org>
Tested-by: Dylan Reid <dgreid@chromium.org>
Reviewed-by: Dylan Reid <dgreid@chromium.org>
Reviewed-by: Zach Reizner <zachr@chromium.org>
This commit is contained in:
Stephen Barber 2017-06-15 23:19:23 -07:00 committed by chrome-bot
parent 648a58d248
commit d02ae30698
2 changed files with 435 additions and 0 deletions

10
vhost/Cargo.toml Normal file
View file

@ -0,0 +1,10 @@
[package]
name = "vhost"
version = "0.1.0"
authors = ["The Chromium OS Authors"]
[dependencies]
libc = "*"
net_util = { path = "../net_util" }
sys_util = { path = "../sys_util" }
virtio_sys = { path = "../virtio_sys" }

425
vhost/src/lib.rs Normal file
View file

@ -0,0 +1,425 @@
// 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.
extern crate libc;
extern crate net_util;
extern crate sys_util;
extern crate virtio_sys;
use std::fs::File;
use std::mem;
use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
use std::ptr::null;
use sys_util::{EventFd, GuestAddress, GuestMemory, GuestMemoryError, Error as SysError};
use sys_util::{ioctl, ioctl_with_ref, ioctl_with_mut_ref, ioctl_with_ptr};
#[derive(Debug)]
pub enum Error {
/// Error opening /dev/host-net.
VhostOpen(SysError),
/// Error while running ioctl.
IoctlError(SysError),
/// Invalid queue.
InvalidQueue,
/// Invalid descriptor table address.
DescriptorTableAddress(GuestMemoryError),
/// Invalid used address.
UsedAddress(GuestMemoryError),
/// Invalid available address.
AvailAddress(GuestMemoryError),
/// Invalid log address.
LogAddress(GuestMemoryError),
}
pub type Result<T> = std::result::Result<T, Error>;
fn ioctl_result<T>() -> Result<T> {
Err(Error::IoctlError(SysError::last()))
}
/// Handle to run VHOST_NET ioctls.
///
/// This provides a simple wrapper around a VHOST_NET file descriptor and
/// methods that safely run ioctls on that file descriptor.
pub struct VhostNet {
// vhost_net must be dropped first, which will stop and tear down the
// vhost_net worker before GuestMemory can potentially be unmapped.
vhost_net: File,
mem: GuestMemory,
}
impl VhostNet {
/// Opens /dev/vhost-net and holds a file descriptor open for it.
///
/// # Arguments
/// * `mem` - Guest memory mapping.
pub fn new(mem: &GuestMemory) -> Result<VhostNet> {
// Open calls are safe because we give a constant nul-terminated
// string and verify the result.
let fd = unsafe {
libc::open(b"/dev/vhost-net\0".as_ptr() as *const i8,
libc::O_RDONLY | libc::O_WRONLY | libc::O_NONBLOCK | libc::O_CLOEXEC)
};
if fd < 0 {
return Err(Error::VhostOpen(SysError::last()));
}
Ok(VhostNet {
// There are no other users of this fd, so this is safe.
vhost_net: unsafe { File::from_raw_fd(fd) },
mem: mem.clone(),
})
}
/// Set the current process as the owner of this file descriptor.
/// This must be run before any other vhost ioctls.
pub fn set_owner(&self) -> Result<()> {
// This ioctl is called on a valid vhost_net fd and has its
// return value checked.
let ret = unsafe { ioctl(&self.vhost_net, virtio_sys::VHOST_SET_OWNER()) };
if ret < 0 {
return ioctl_result();
}
Ok(())
}
/// Get a bitmask of supported virtio/vhost features.
pub fn get_features(&self) -> Result<u64> {
let mut avail_features: u64 = 0;
// This ioctl is called on a valid vhost_net fd and has its
// return value checked.
let ret = unsafe {
ioctl_with_mut_ref(&self.vhost_net,
virtio_sys::VHOST_GET_FEATURES(),
&mut avail_features)
};
if ret < 0 {
return ioctl_result();
}
Ok(avail_features)
}
/// Inform VHOST_NET which features to enable. This should be a subset of
/// supported features from VHOST_GET_FEATURES.
///
/// # Arguments
/// * `features` - Bitmask of features to set.
pub fn set_features(&self, features: u64) -> Result<()> {
// This ioctl is called on a valid vhost_net fd and has its
// return value checked.
let ret =
unsafe { ioctl_with_ref(&self.vhost_net, virtio_sys::VHOST_SET_FEATURES(), &features) };
if ret < 0 {
return ioctl_result();
}
Ok(())
}
/// Set the guest memory mappings for vhost to use.
pub fn set_mem_table(&self) -> Result<()> {
let num_regions = self.mem.num_regions();
let vec_size_bytes = mem::size_of::<virtio_sys::vhost_memory>() +
(num_regions * mem::size_of::<virtio_sys::vhost_memory_region>());
let mut bytes: Vec<u8> = vec![0; vec_size_bytes];
// Convert bytes pointer to a vhost_memory mut ref. The vector has been
// sized correctly to ensure it can hold vhost_memory and N regions.
let vhost_memory: &mut virtio_sys::vhost_memory =
unsafe { &mut *(bytes.as_mut_ptr() as *mut virtio_sys::vhost_memory) };
vhost_memory.nregions = num_regions as u32;
// regions is a zero-length array, so taking a mut slice requires that
// we correctly specify the size to match the amount of backing memory.
let vhost_regions = unsafe { vhost_memory.regions.as_mut_slice(num_regions as usize) };
let _ = self.mem
.with_regions_mut::<_, ()>(|index, guest_addr, size, host_addr| {
vhost_regions[index] = virtio_sys::vhost_memory_region {
guest_phys_addr: guest_addr.offset() as u64,
memory_size: size as u64,
userspace_addr: host_addr as u64,
flags_padding: 0u64,
};
Ok(())
});
// This ioctl is called with a pointer that is valid for the lifetime
// of this function. The kernel will make its own copy of the memory
// tables. As always, check the return value.
let ret = unsafe {
ioctl_with_ptr(&self.vhost_net,
virtio_sys::VHOST_SET_MEM_TABLE(),
bytes.as_ptr())
};
if ret < 0 {
return ioctl_result();
}
Ok(())
}
/// Set the number of descriptors in the vring.
///
/// # Arguments
/// * `queue_index` - Index of the queue to set descriptor count for.
/// * `num` - Number of descriptors in the queue.
pub fn set_vring_num(&self, queue_index: usize, num: u16) -> Result<()> {
let vring_state = virtio_sys::vhost_vring_state {
index: queue_index as u32,
num: num as u32,
};
// This ioctl is called on a valid vhost_net fd and has its
// return value checked.
let ret = unsafe {
ioctl_with_ref(&self.vhost_net,
virtio_sys::VHOST_SET_VRING_NUM(),
&vring_state)
};
if ret < 0 {
return ioctl_result();
}
Ok(())
}
// TODO(smbarber): This is copypasta. Eliminate the copypasta.
fn is_valid(&self,
queue_max_size: u16,
queue_size: u16,
desc_addr: GuestAddress,
avail_addr: GuestAddress,
used_addr: GuestAddress) -> bool {
let desc_table_size = 16 * queue_size as usize;
let avail_ring_size = 6 + 2 * queue_size as usize;
let used_ring_size = 6 + 8 * queue_size as usize;
if queue_size > queue_max_size || queue_size == 0 ||
(queue_size & (queue_size - 1)) != 0 {
false
} else if desc_addr
.checked_add(desc_table_size)
.map_or(true, |v| !self.mem.address_in_range(v)) {
false
} else if avail_addr
.checked_add(avail_ring_size)
.map_or(true, |v| !self.mem.address_in_range(v)) {
false
} else if used_addr
.checked_add(used_ring_size)
.map_or(true, |v| !self.mem.address_in_range(v)) {
false
} else {
true
}
}
/// Set the addresses for a given vring.
///
/// # Arguments
/// * `queue_max_size` - Maximum queue size supported by the device.
/// * `queue_size` - Actual queue size negotiated by the driver.
/// * `queue_index` - Index of the queue to set addresses for.
/// * `flags` - Bitmask of vring flags.
/// * `desc_addr` - Descriptor table address.
/// * `used_addr` - Used ring buffer address.
/// * `avail_addr` - Available ring buffer address.
/// * `log_addr` - Optional address for logging.
pub fn set_vring_addr(&self,
queue_max_size: u16,
queue_size: u16,
queue_index: usize,
flags: u32,
desc_addr: GuestAddress,
used_addr: GuestAddress,
avail_addr: GuestAddress,
log_addr: Option<GuestAddress>)
-> Result<()> {
// TODO(smbarber): Refactor out virtio from crosvm so we can
// validate a Queue struct directly.
if !self.is_valid(queue_max_size, queue_size, desc_addr, used_addr, avail_addr) {
return Err(Error::InvalidQueue);
}
let desc_addr = self.mem
.get_host_address(desc_addr)
.map_err(Error::DescriptorTableAddress)?;
let used_addr = self.mem
.get_host_address(used_addr)
.map_err(Error::UsedAddress)?;
let avail_addr = self.mem
.get_host_address(avail_addr)
.map_err(Error::AvailAddress)?;
let log_addr = match log_addr {
None => null(),
Some(a) => {
self.mem
.get_host_address(a)
.map_err(Error::LogAddress)?
},
};
let vring_addr = virtio_sys::vhost_vring_addr {
index: queue_index as u32,
flags: flags,
desc_user_addr: desc_addr as u64,
used_user_addr: used_addr as u64,
avail_user_addr: avail_addr as u64,
log_guest_addr: log_addr as u64,
};
// This ioctl is called on a valid vhost_net fd and has its
// return value checked.
let ret = unsafe {
ioctl_with_ref(&self.vhost_net,
virtio_sys::VHOST_SET_VRING_ADDR(),
&vring_addr)
};
if ret < 0 {
return ioctl_result();
}
Ok(())
}
/// Set the first index to look for available descriptors.
///
/// # Arguments
/// * `queue_index` - Index of the queue to modify.
/// * `num` - Index where available descriptors start.
pub fn set_vring_base(&self, queue_index: usize, num: u16) -> Result<()> {
let vring_state = virtio_sys::vhost_vring_state {
index: queue_index as u32,
num: num as u32,
};
// This ioctl is called on a valid vhost_net fd and has its
// return value checked.
let ret = unsafe {
ioctl_with_ref(&self.vhost_net,
virtio_sys::VHOST_SET_VRING_BASE(),
&vring_state)
};
if ret < 0 {
return ioctl_result();
}
Ok(())
}
/// Set the eventfd to trigger when buffers have been used by the host.
///
/// # Arguments
/// * `queue_index` - Index of the queue to modify.
/// * `fd` - EventFd to trigger.
pub fn set_vring_call(&self, queue_index: usize, fd: &EventFd) -> Result<()> {
let vring_file = virtio_sys::vhost_vring_file {
index: queue_index as u32,
fd: fd.as_raw_fd(),
};
// This ioctl is called on a valid vhost_net fd and has its
// return value checked.
let ret = unsafe {
ioctl_with_ref(&self.vhost_net,
virtio_sys::VHOST_SET_VRING_CALL(),
&vring_file)
};
if ret < 0 {
return ioctl_result();
}
Ok(())
}
/// Set the eventfd that will be signaled by the guest when buffers are
/// available for the host to process.
///
/// # Arguments
/// * `queue_index` - Index of the queue to modify.
/// * `fd` - EventFd that will be signaled from guest.
pub fn set_vring_kick(&self, queue_index: usize, fd: &EventFd) -> Result<()> {
let vring_file = virtio_sys::vhost_vring_file {
index: queue_index as u32,
fd: fd.as_raw_fd(),
};
// This ioctl is called on a valid vhost_net fd and has its
// return value checked.
let ret = unsafe {
ioctl_with_ref(&self.vhost_net,
virtio_sys::VHOST_SET_VRING_KICK(),
&vring_file)
};
if ret < 0 {
return ioctl_result();
}
Ok(())
}
/// Set the tap file descriptor that will serve as the VHOST_NET backend.
/// This will start the vhost worker for the given queue.
///
/// # Arguments
/// * `queue_index` - Index of the queue to modify.
/// * `fd` - Tap interface that will be used as the backend.
pub fn net_set_backend(&self, queue_index: usize, fd: &net_util::Tap) -> Result<()> {
let vring_file = virtio_sys::vhost_vring_file {
index: queue_index as u32,
fd: fd.as_raw_fd(),
};
// This ioctl is called on a valid vhost_net fd and has its
// return value checked.
let ret = unsafe {
ioctl_with_ref(&self.vhost_net,
virtio_sys::VHOST_NET_SET_BACKEND(),
&vring_file)
};
if ret < 0 {
return ioctl_result();
}
Ok(())
}
}
impl AsRawFd for VhostNet {
fn as_raw_fd(&self) -> RawFd {
self.vhost_net.as_raw_fd()
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::result;
use sys_util::{GuestAddress, GuestMemory, GuestMemoryError};
fn create_guest_memory() -> result::Result<GuestMemory, GuestMemoryError> {
let start_addr1 = GuestAddress(0x0);
let start_addr2 = GuestAddress(0x100);
GuestMemory::new(&vec![(start_addr1, 0x100), (start_addr2, 0x400)])
}
#[test]
fn open_vhostnet() {
let gm = create_guest_memory().unwrap();
VhostNet::new(&gm).unwrap();
}
#[test]
fn set_owner() {
let gm = create_guest_memory().unwrap();
let vhost_net = VhostNet::new(&gm).unwrap();
vhost_net.set_owner().unwrap();
}
#[test]
fn get_features() {
let gm = create_guest_memory().unwrap();
let vhost_net = VhostNet::new(&gm).unwrap();
vhost_net.get_features().unwrap();
}
#[test]
fn set_features() {
let gm = create_guest_memory().unwrap();
let vhost_net = VhostNet::new(&gm).unwrap();
vhost_net.set_features(0).unwrap();
}
}