diff --git a/Cargo.toml b/Cargo.toml index c0b8efd..2a8c2ab 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,7 @@ default = [] vhost-vsock = [] vhost-kern = [] vhost-vdpa = ["vhost-kern"] +vhost-net = ["vhost-kern"] vhost-user = [] vhost-user-master = ["vhost-user"] vhost-user-slave = ["vhost-user"] diff --git a/src/lib.rs b/src/lib.rs index 2320fe8..f8be913 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -40,6 +40,8 @@ extern crate vmm_sys_util; mod backend; pub use backend::*; +#[cfg(feature = "vhost-net")] +pub mod net; #[cfg(feature = "vhost-vdpa")] pub mod vdpa; #[cfg(feature = "vhost-kern")] diff --git a/src/net.rs b/src/net.rs new file mode 100644 index 0000000..06a5e47 --- /dev/null +++ b/src/net.rs @@ -0,0 +1,19 @@ +// Copyright (C) 2021 Alibaba Cloud Computing. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause + +//! Trait to control vhost-net backend drivers. + +use std::fs::File; + +use crate::backend::VhostBackend; +use crate::Result; + +/// Trait to control vhost-net backend drivers. +pub trait VhostNet: VhostBackend { + /// Set fd as VHOST_NET backend. + /// + /// # Arguments + /// * `queue_index` - Index of the virtqueue + /// * `fd` - The file descriptor which servers as the backend + fn set_backend(&self, queue_idx: usize, fd: Option<&File>) -> Result<()>; +} diff --git a/src/vhost_kern/mod.rs b/src/vhost_kern/mod.rs index b179814..42a1450 100644 --- a/src/vhost_kern/mod.rs +++ b/src/vhost_kern/mod.rs @@ -28,6 +28,8 @@ use super::{ pub mod vhost_binding; use self::vhost_binding::*; +#[cfg(feature = "vhost-net")] +pub mod net; #[cfg(feature = "vhost-vdpa")] pub mod vdpa; #[cfg(feature = "vhost-vsock")] diff --git a/src/vhost_kern/net.rs b/src/vhost_kern/net.rs new file mode 100644 index 0000000..f066291 --- /dev/null +++ b/src/vhost_kern/net.rs @@ -0,0 +1,176 @@ +// Copyright (C) 2021 Alibaba Cloud Computing. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause + +//! Kernel-based vhost-net backend + +use std::fs::{File, OpenOptions}; +use std::os::unix::fs::OpenOptionsExt; +use std::os::unix::io::{AsRawFd, RawFd}; + +use vm_memory::GuestAddressSpace; +use vmm_sys_util::ioctl::ioctl_with_ref; + +use super::vhost_binding::*; +use super::{ioctl_result, Error, Result, VhostKernBackend}; + +use crate::net::*; + +const VHOST_NET_PATH: &str = "/dev/vhost-net"; + +/// Handle for running VHOST_NET ioctls +pub struct Net { + fd: File, + mem: AS, +} + +impl Net { + /// Open a handle to a new VHOST-NET instance. + pub fn new(mem: AS) -> Result { + Ok(Net { + fd: OpenOptions::new() + .read(true) + .write(true) + .custom_flags(libc::O_CLOEXEC | libc::O_NONBLOCK) + .open(VHOST_NET_PATH) + .map_err(Error::VhostOpen)?, + mem, + }) + } +} + +impl VhostNet for Net { + fn set_backend(&self, queue_index: usize, fd: Option<&File>) -> Result<()> { + let vring_file = vhost_vring_file { + index: queue_index as u32, + fd: fd.map_or(-1, |v| v.as_raw_fd()), + }; + + let ret = unsafe { ioctl_with_ref(self, VHOST_NET_SET_BACKEND(), &vring_file) }; + ioctl_result(ret, ()) + } +} + +impl VhostKernBackend for Net { + type AS = AS; + + fn mem(&self) -> &Self::AS { + &self.mem + } +} + +impl AsRawFd for Net { + fn as_raw_fd(&self) -> RawFd { + self.fd.as_raw_fd() + } +} + +#[cfg(test)] +mod tests { + use vm_memory::{GuestAddress, GuestMemory, GuestMemoryMmap}; + use vmm_sys_util::eventfd::EventFd; + + use super::*; + use crate::{ + VhostBackend, VhostUserDirtyLogRegion, VhostUserMemoryRegionInfo, VringConfigData, + }; + use serial_test::serial; + + #[test] + #[serial] + fn test_net_new_device() { + let m = GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 0x10_0000)]).unwrap(); + let net = Net::new(&m).unwrap(); + + assert!(net.as_raw_fd() >= 0); + assert!(net.mem().find_region(GuestAddress(0x100)).is_some()); + assert!(net.mem().find_region(GuestAddress(0x10_0000)).is_none()); + } + + #[test] + #[serial] + fn test_net_is_valid() { + let m = GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 0x10_0000)]).unwrap(); + let net = Net::new(&m).unwrap(); + + let mut config = VringConfigData { + queue_max_size: 32, + queue_size: 32, + flags: 0, + desc_table_addr: 0x1000, + used_ring_addr: 0x2000, + avail_ring_addr: 0x3000, + log_addr: None, + }; + assert_eq!(net.is_valid(&config), true); + + config.queue_size = 0; + assert_eq!(net.is_valid(&config), false); + config.queue_size = 31; + assert_eq!(net.is_valid(&config), false); + config.queue_size = 33; + assert_eq!(net.is_valid(&config), false); + } + + #[test] + #[serial] + fn test_net_ioctls() { + let m = GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 0x10_0000)]).unwrap(); + let net = Net::new(&m).unwrap(); + let backend = OpenOptions::new() + .read(true) + .write(true) + .open("/dev/null") + .unwrap(); + + let features = net.get_features().unwrap(); + net.set_features(features).unwrap(); + + net.set_owner().unwrap(); + + net.set_mem_table(&[]).unwrap_err(); + + let region = VhostUserMemoryRegionInfo { + guest_phys_addr: 0x0, + memory_size: 0x10_0000, + userspace_addr: m.get_host_address(GuestAddress(0x0)).unwrap() as u64, + mmap_offset: 0, + mmap_handle: -1, + }; + net.set_mem_table(&[region]).unwrap(); + + net.set_log_base( + 0x4000, + Some(VhostUserDirtyLogRegion { + mmap_size: 0x1000, + mmap_offset: 0x10, + mmap_handle: 1, + }), + ) + .unwrap_err(); + net.set_log_base(0x4000, None).unwrap(); + + let eventfd = EventFd::new(0).unwrap(); + net.set_log_fd(eventfd.as_raw_fd()).unwrap(); + + net.set_vring_num(0, 32).unwrap(); + + let config = VringConfigData { + queue_max_size: 32, + queue_size: 32, + flags: 0, + desc_table_addr: 0x1000, + used_ring_addr: 0x2000, + avail_ring_addr: 0x3000, + log_addr: None, + }; + net.set_vring_addr(0, &config).unwrap(); + net.set_vring_base(0, 1).unwrap(); + net.set_vring_call(0, &eventfd).unwrap(); + net.set_vring_kick(0, &eventfd).unwrap(); + net.set_vring_err(0, &eventfd).unwrap(); + assert_eq!(net.get_vring_base(0).unwrap(), 1); + + net.set_backend(0, Some(&backend)).unwrap_err(); + net.set_backend(0, None).unwrap(); + } +}