From 60686585843d741fb2c3793d7b031b343ea47a2b Mon Sep 17 00:00:00 2001 From: Keiichi Watanabe Date: Fri, 12 Mar 2021 04:53:51 +0900 Subject: [PATCH] devices: virtio: Add vhost-user-net master device Add vhost-user virtio-net master device. Ctrl queue will be supported in a separate CL. BUG=b:179755448 TEST=curl/ping worked with cloud-hypervisor's backend Change-Id: Ibda3d93457be9841748b649e492d0fd11969fd4f Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/2717904 Tested-by: Keiichi Watanabe Tested-by: kokoro Commit-Queue: Keiichi Watanabe Reviewed-by: Chirantan Ekbote --- devices/src/virtio/net.rs | 2 +- devices/src/virtio/vhost/user/mod.rs | 2 + devices/src/virtio/vhost/user/net.rs | 185 +++++++++++++++++++++++++++ src/crosvm.rs | 2 + src/linux.rs | 35 ++++- src/main.rs | 4 + 6 files changed, 227 insertions(+), 3 deletions(-) create mode 100644 devices/src/virtio/vhost/user/net.rs diff --git a/devices/src/virtio/net.rs b/devices/src/virtio/net.rs index 1c1c225cce..b88dc44ae9 100644 --- a/devices/src/virtio/net.rs +++ b/devices/src/virtio/net.rs @@ -134,7 +134,7 @@ fn virtio_features_to_tap_offload(features: u64) -> c_uint { #[derive(Debug, Clone, Copy, Default)] #[repr(C)] -struct VirtioNetConfig { +pub(crate) struct VirtioNetConfig { mac: [u8; 6], status: Le16, max_vq_pairs: Le16, diff --git a/devices/src/virtio/vhost/user/mod.rs b/devices/src/virtio/vhost/user/mod.rs index d210c1e177..b3bf607d8a 100644 --- a/devices/src/virtio/vhost/user/mod.rs +++ b/devices/src/virtio/vhost/user/mod.rs @@ -4,9 +4,11 @@ mod block; mod handler; +mod net; mod worker; pub use self::block::*; +pub use self::net::*; use remain::sorted; use thiserror::Error as ThisError; diff --git a/devices/src/virtio/vhost/user/net.rs b/devices/src/virtio/vhost/user/net.rs new file mode 100644 index 0000000000..fe3deccf48 --- /dev/null +++ b/devices/src/virtio/vhost/user/net.rs @@ -0,0 +1,185 @@ +// Copyright 2021 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::cell::RefCell; +use std::os::unix::net::UnixStream; +use std::path::Path; +use std::thread; +use std::u32; + +use base::{error, Event, RawDescriptor}; +use cros_async::Executor; +use virtio_sys::virtio_net; +use virtio_sys::virtio_ring::VIRTIO_RING_F_EVENT_IDX; +use vm_memory::GuestMemory; +use vmm_vhost::vhost_user::message::{VhostUserProtocolFeatures, VhostUserVirtioFeatures}; +use vmm_vhost::vhost_user::Master; + +use crate::virtio::vhost::user::handler::VhostUserHandler; +use crate::virtio::vhost::user::worker::Worker; +use crate::virtio::vhost::user::Error; +use crate::virtio::{Interrupt, Queue, VirtioDevice, VirtioNetConfig, TYPE_NET}; + +type Result = std::result::Result; + +const QUEUE_SIZE: u16 = 256; + +pub struct Net { + kill_evt: Option, + worker_thread: Option>, + handler: RefCell, + queue_sizes: Vec, +} + +impl Net { + pub fn new>(base_features: u64, socket_path: P) -> Result { + let socket = UnixStream::connect(&socket_path).map_err(Error::SocketConnect)?; + let vhost_user_net = Master::from_stream(socket, 16 /* # of queues */); + + // TODO(b/182430355): Support VIRTIO_NET_F_CTRL_VQ and VIRTIO_NET_F_CTRL_GUEST_OFFLOADS. + let allow_features = 1 << crate::virtio::VIRTIO_F_VERSION_1 + | 1 << virtio_net::VIRTIO_NET_F_CSUM + | 1 << virtio_net::VIRTIO_NET_F_GUEST_CSUM + | 1 << virtio_net::VIRTIO_NET_F_GUEST_TSO4 + | 1 << virtio_net::VIRTIO_NET_F_GUEST_UFO + | 1 << virtio_net::VIRTIO_NET_F_HOST_TSO4 + | 1 << virtio_net::VIRTIO_NET_F_HOST_UFO + | 1 << virtio_net::VIRTIO_NET_F_MQ + | 1 << VIRTIO_RING_F_EVENT_IDX + | VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits(); + let init_features = base_features | VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits(); + let allow_protocol_features = + VhostUserProtocolFeatures::MQ | VhostUserProtocolFeatures::CONFIG; + + let mut handler = VhostUserHandler::new( + vhost_user_net, + allow_features, + init_features, + allow_protocol_features, + )?; + let queue_sizes = handler.queue_sizes(QUEUE_SIZE, 2 /* 1 rx + 1 tx */)?; + + Ok(Net { + kill_evt: None, + worker_thread: None, + handler: RefCell::new(handler), + queue_sizes, + }) + } +} + +impl Drop for Net { + fn drop(&mut self) { + if let Some(kill_evt) = self.kill_evt.take() { + // Ignore the result because there is nothing we can do about it. + let _ = kill_evt.write(1); + } + + if let Some(worker_thread) = self.worker_thread.take() { + let _ = worker_thread.join(); + } + } +} + +impl VirtioDevice for Net { + fn keep_rds(&self) -> Vec { + Vec::new() + } + + fn features(&self) -> u64 { + self.handler.borrow().avail_features + } + + fn ack_features(&mut self, features: u64) { + if let Err(e) = self.handler.borrow_mut().ack_features(features) { + error!("failed to enable features 0x{:x}: {}", features, e); + } + } + + fn device_type(&self) -> u32 { + TYPE_NET + } + + fn queue_max_sizes(&self) -> &[u16] { + self.queue_sizes.as_slice() + } + + fn read_config(&self, offset: u64, data: &mut [u8]) { + if let Err(e) = self + .handler + .borrow_mut() + .read_config::(offset, data) + { + error!("failed to read config: {}", e); + } + } + + fn activate( + &mut self, + mem: GuestMemory, + interrupt: Interrupt, + queues: Vec, + queue_evts: Vec, + ) { + // TODO(b/182430355): Remove this check once ctrlq is supported. + if queues.len() % 2 != 0 { + error!( + "The number of queues must be an even number but {}", + queues.len() + ); + } + + if let Err(e) = self + .handler + .borrow_mut() + .activate(&mem, &interrupt, &queues, &queue_evts) + { + error!("failed to activate queues: {}", e); + return; + } + + let (self_kill_evt, kill_evt) = match Event::new().and_then(|e| Ok((e.try_clone()?, e))) { + Ok(v) => v, + Err(e) => { + error!("failed creating kill Event pair: {}", e); + return; + } + }; + self.kill_evt = Some(self_kill_evt); + + let worker_result = thread::Builder::new() + .name("vhost_user_virtio_net".to_string()) + .spawn(move || { + let ex = Executor::new().expect("failed to create an executor"); + let mut worker = Worker { + queues, + mem, + kill_evt, + }; + if let Err(e) = worker.run(&ex, interrupt) { + error!("failed to start a worker: {}", e); + } + worker + }); + + match worker_result { + Err(e) => { + error!("failed to spawn virtio_net worker: {}", e); + return; + } + Ok(join_handle) => { + self.worker_thread = Some(join_handle); + } + } + } + + fn reset(&mut self) -> bool { + if let Err(e) = self.handler.borrow_mut().reset(self.queue_sizes.len()) { + error!("Failed to reset net device: {}", e); + false + } else { + true + } + } +} diff --git a/src/crosvm.rs b/src/crosvm.rs index 4d41cc5b18..3578deb951 100644 --- a/src/crosvm.rs +++ b/src/crosvm.rs @@ -241,6 +241,7 @@ pub struct Config { pub gdb: Option, pub balloon_bias: i64, pub vhost_user_blk: Vec, + pub vhost_user_net: Vec, } impl Default for Config { @@ -306,6 +307,7 @@ impl Default for Config { gdb: None, balloon_bias: 0, vhost_user_blk: Vec::new(), + vhost_user_net: Vec::new(), } } } diff --git a/src/linux.rs b/src/linux.rs index 0e6e699e64..d0567aba93 100644 --- a/src/linux.rs +++ b/src/linux.rs @@ -33,7 +33,9 @@ use libc::{self, c_int, gid_t, uid_t}; use acpi_tables::sdt::SDT; use base::net::{UnixSeqpacket, UnixSeqpacketListener, UnlinkUnixSeqpacketListener}; -use devices::virtio::vhost::user::{Block as VhostUserBlock, Error as VhostUserBlockError}; +use devices::virtio::vhost::user::{ + Block as VhostUserBlock, Error as VhostUserError, Net as VhostUserNet, +}; #[cfg(feature = "gpu")] use devices::virtio::EventDevice; use devices::virtio::{self, Console, VirtioDevice}; @@ -192,7 +194,9 @@ pub enum Error { Timer(base::Error), ValidateRawDescriptor(base::Error), VhostNetDeviceNew(virtio::vhost::Error), - VhostUserBlockDeviceNew(VhostUserBlockError), + VhostUserBlockDeviceNew(VhostUserError), + VhostUserNetDeviceNew(VhostUserError), + VhostUserNetWithNetArgs, VhostVsockDeviceNew(virtio::vhost::Error), VirtioPciDev(base::Error), WaitContextAdd(base::Error), @@ -316,6 +320,15 @@ impl Display for Error { VhostUserBlockDeviceNew(e) => { write!(f, "failed to set up vhost-user block device: {}", e) } + VhostUserNetDeviceNew(e) => { + write!(f, "failed to set up vhost-user net device: {}", e) + } + VhostUserNetWithNetArgs => { + write!( + f, + "vhost-user-net cannot be used with any of --host_ip, --netmask or --mac" + ) + } VhostVsockDeviceNew(e) => write!(f, "failed to set up virtual socket device: {}", e), VirtioPciDev(e) => write!(f, "failed to create virtio pci dev: {}", e), WaitContextAdd(e) => write!(f, "failed to add descriptor to wait context: {}", e), @@ -820,6 +833,17 @@ fn create_net_device( }) } +fn create_vhost_user_net_device(cfg: &Config, opt: &VhostUserOption) -> DeviceResult { + let dev = VhostUserNet::new(virtio::base_features(cfg.protected_vm), &opt.socket) + .map_err(Error::VhostUserNetDeviceNew)?; + + Ok(VirtioDeviceStub { + dev: Box::new(dev), + // no sandbox here because virtqueue handling is exported to a different process. + jail: None, + }) +} + #[cfg(feature = "gpu")] fn create_gpu_device( cfg: &Config, @@ -1425,9 +1449,16 @@ fn create_virtio_devices( if let (Some(host_ip), Some(netmask), Some(mac_address)) = (cfg.host_ip, cfg.netmask, cfg.mac_address) { + if !cfg.vhost_user_net.is_empty() { + return Err(Error::VhostUserNetWithNetArgs); + } devs.push(create_net_device(cfg, host_ip, netmask, mac_address, mem)?); } + for net in &cfg.vhost_user_net { + devs.push(create_vhost_user_net_device(cfg, net)?); + } + #[cfg_attr(not(feature = "gpu"), allow(unused_mut))] let mut resource_bridges = Vec::::new(); diff --git a/src/main.rs b/src/main.rs index 3b6377bb34..963fe6dcec 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1588,6 +1588,9 @@ fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument:: "vhost-user-blk" => cfg.vhost_user_blk.push(VhostUserOption { socket: PathBuf::from(value.unwrap()), }), + "vhost-user-net" => cfg.vhost_user_net.push(VhostUserOption { + socket: PathBuf::from(value.unwrap()), + }), "help" => return Err(argument::Error::PrintHelp), _ => unreachable!(), } @@ -1798,6 +1801,7 @@ writeback=BOOL - Indicates whether the VM can use writeback caching (default: fa Argument::value("gdb", "PORT", "(EXPERIMENTAL) gdb on the given port"), Argument::value("balloon_bias_mib", "N", "Amount to bias balance of memory between host and guest as the balloon inflates, in MiB."), Argument::value("vhost-user-blk", "SOCKET_PATH", "Path to a socket for vhost-user block"), + Argument::value("vhost-user-net", "SOCKET_PATH", "Path to a socket for vhost-user net"), Argument::short_flag('h', "help", "Print help message.")]; let mut cfg = Config::default();