From 6ae2597d1956aef34762243e3e38b7ce16d494ac Mon Sep 17 00:00:00 2001 From: Cathy Zhang Date: Thu, 9 Jan 2020 19:54:40 +0800 Subject: [PATCH] vm-virtio: Create new module to abstract common parts for net devices There are some common logic shared among virtio-net device, vhost-user-net device and vhost-user-net backend, abstract those parts into net_util.rs to improve code maintainability and readability. Signed-off-by: Cathy Zhang --- vm-virtio/src/lib.rs | 2 + vm-virtio/src/net_util.rs | 222 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 224 insertions(+) create mode 100644 vm-virtio/src/net_util.rs diff --git a/vm-virtio/src/lib.rs b/vm-virtio/src/lib.rs index 5956c592b..593f34e7b 100755 --- a/vm-virtio/src/lib.rs +++ b/vm-virtio/src/lib.rs @@ -30,6 +30,7 @@ pub mod block; mod console; mod iommu; pub mod net; +pub mod net_util; mod pmem; mod queue; mod rng; @@ -43,6 +44,7 @@ pub use self::console::*; pub use self::device::*; pub use self::iommu::*; pub use self::net::*; +pub use self::net_util::*; pub use self::pmem::*; pub use self::queue::*; pub use self::rng::*; diff --git a/vm-virtio/src/net_util.rs b/vm-virtio/src/net_util.rs new file mode 100644 index 000000000..307dbacfa --- /dev/null +++ b/vm-virtio/src/net_util.rs @@ -0,0 +1,222 @@ +// Copyright (c) 2019 Intel Corporation. All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause + +use super::{DescriptorChain, DeviceEventT, Queue}; +use net_util::{MacAddr, Tap, TapError, MAC_ADDR_LEN}; +use std::cmp; +use std::io::Write; +use std::mem; +use std::net::Ipv4Addr; +use virtio_bindings::bindings::virtio_net::*; +use vm_memory::{Bytes, GuestAddress, GuestMemoryMmap}; + +type Result = std::result::Result; + +/// The maximum buffer size when segmentation offload is enabled. This +/// includes the 12-byte virtio net header. +/// http://docs.oasis-open.org/virtio/virtio/v1.0/virtio-v1.0.html#x1-1740003 +const MAX_BUFFER_SIZE: usize = 65562; + +// The guest has made a buffer available to receive a frame into. +pub const RX_QUEUE_EVENT: DeviceEventT = 0; +// The transmit queue has a frame that is ready to send from the guest. +pub const TX_QUEUE_EVENT: DeviceEventT = 1; +// A frame is available for reading from the tap device to receive in the guest. +pub const RX_TAP_EVENT: DeviceEventT = 2; +// The device has been dropped. +pub const KILL_EVENT: DeviceEventT = 3; +// The device should be paused. +pub const PAUSE_EVENT: DeviceEventT = 4; +// Number of DeviceEventT events supported by this implementation. +pub const NET_EVENTS_COUNT: usize = 5; + +#[derive(Debug)] +pub enum Error { + /// Open tap device failed. + TapOpen(TapError), + /// Setting tap IP failed. + TapSetIp(TapError), + /// Setting tap netmask failed. + TapSetNetmask(TapError), + /// Setting tap interface offload flags failed. + TapSetOffload(TapError), + /// Setting vnet header size failed. + TapSetVnetHdrSize(TapError), + /// Enabling tap interface failed. + TapEnable(TapError), +} + +#[derive(Clone)] +pub struct TxVirtio { + pub iovec: Vec<(GuestAddress, usize)>, + pub frame_buf: [u8; MAX_BUFFER_SIZE], +} + +impl Default for TxVirtio { + fn default() -> Self { + Self::new() + } +} + +impl TxVirtio { + pub fn new() -> Self { + TxVirtio { + iovec: Vec::new(), + frame_buf: [0u8; MAX_BUFFER_SIZE], + } + } + + pub fn process_desc_chain(&mut self, mem: &GuestMemoryMmap, tap: &mut Tap, queue: &mut Queue) { + while let Some(avail_desc) = queue.iter(&mem).next() { + let head_index = avail_desc.index; + let mut read_count = 0; + let mut next_desc = Some(avail_desc); + + self.iovec.clear(); + while let Some(desc) = next_desc { + if desc.is_write_only() { + break; + } + self.iovec.push((desc.addr, desc.len as usize)); + read_count += desc.len as usize; + next_desc = desc.next_descriptor(); + } + + read_count = 0; + // Copy buffer from across multiple descriptors. + // TODO(performance - Issue #420): change this to use `writev()` instead of `write()` + // and get rid of the intermediate buffer. + for (desc_addr, desc_len) in self.iovec.drain(..) { + let limit = cmp::min((read_count + desc_len) as usize, self.frame_buf.len()); + + let read_result = + mem.read_slice(&mut self.frame_buf[read_count..limit as usize], desc_addr); + match read_result { + Ok(_) => { + // Increment by number of bytes actually read + read_count += limit - read_count; + } + Err(e) => { + println!("Failed to read slice: {:?}", e); + break; + } + } + } + + let write_result = tap.write(&self.frame_buf[..read_count]); + match write_result { + Ok(_) => {} + Err(e) => { + println!("net: tx: error failed to write to tap: {}", e); + } + }; + queue.add_used(&mem, head_index, 0); + } + } +} + +#[derive(Clone)] +pub struct RxVirtio { + pub deferred_frame: bool, + pub deferred_irqs: bool, + pub bytes_read: usize, + pub frame_buf: [u8; MAX_BUFFER_SIZE], +} + +impl Default for RxVirtio { + fn default() -> Self { + Self::new() + } +} + +impl RxVirtio { + pub fn new() -> Self { + RxVirtio { + deferred_frame: false, + deferred_irqs: false, + bytes_read: 0, + frame_buf: [0u8; MAX_BUFFER_SIZE], + } + } + + pub fn process_desc_chain( + &mut self, + mem: &GuestMemoryMmap, + mut next_desc: Option, + queue: &mut Queue, + ) -> bool { + let head_index = next_desc.as_ref().unwrap().index; + let mut write_count = 0; + + // Copy from frame into buffer, which may span multiple descriptors. + loop { + match next_desc { + Some(desc) => { + if !desc.is_write_only() { + break; + } + let limit = cmp::min(write_count + desc.len as usize, self.bytes_read); + let source_slice = &self.frame_buf[write_count..limit]; + let write_result = mem.write_slice(source_slice, desc.addr); + + match write_result { + Ok(_) => { + write_count = limit; + } + Err(e) => { + error!("Failed to write slice: {:?}", e); + break; + } + }; + + if write_count >= self.bytes_read { + break; + } + next_desc = desc.next_descriptor(); + } + None => { + warn!("Receiving buffer is too small to hold frame of current size"); + break; + } + } + } + + queue.add_used(&mem, head_index, write_count as u32); + + // Mark that we have at least one pending packet and we need to interrupt the guest. + self.deferred_irqs = true; + + write_count >= self.bytes_read + } +} + +pub fn build_net_config_space(mac: MacAddr, avail_features: &mut u64) -> Vec { + let mut config_space = Vec::with_capacity(MAC_ADDR_LEN); + unsafe { config_space.set_len(MAC_ADDR_LEN) } + config_space[..].copy_from_slice(mac.get_bytes()); + *avail_features |= 1 << VIRTIO_NET_F_MAC; + + config_space +} + +fn vnet_hdr_len() -> usize { + mem::size_of::() +} + +/// Create a new virtio network device with the given IP address and +/// netmask. +pub fn open_tap(ip_addr: Ipv4Addr, netmask: Ipv4Addr) -> Result { + let vnet_hdr_size = vnet_hdr_len() as i32; + let flag = net_gen::TUN_F_CSUM | net_gen::TUN_F_UFO | net_gen::TUN_F_TSO4 | net_gen::TUN_F_TSO6; + + let tap = Tap::new().map_err(Error::TapOpen)?; + tap.set_ip_addr(ip_addr).map_err(Error::TapSetIp)?; + tap.set_netmask(netmask).map_err(Error::TapSetNetmask)?; + tap.enable().map_err(Error::TapEnable)?; + tap.set_offload(flag).map_err(Error::TapSetOffload)?; + tap.set_vnet_hdr_size(vnet_hdr_size) + .map_err(Error::TapSetVnetHdrSize)?; + + Ok(tap) +}