// 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()), }; // SAFETY: Safe because the vhost-net fd is valid and we check the return value 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!(net.is_valid(&config)); config.queue_size = 0; assert!(!net.is_valid(&config)); config.queue_size = 31; assert!(!net.is_valid(&config)); config.queue_size = 33; assert!(!net.is_valid(&config)); } #[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::new( 0x0, 0x10_0000, m.get_host_address(GuestAddress(0x0)).unwrap() as u64, 0, -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(); } }