From 44306221c14f550de6b60647265ea5fd3a284a38 Mon Sep 17 00:00:00 2001 From: Noah Gold Date: Thu, 24 Mar 2022 11:16:33 -0700 Subject: [PATCH] devices: Upstream Windows vhost-user net device. Splits the vhost-user device into unix & Windows components, and upstreams the Windows side. Note that we can't build devices on Windows yet in crosvm because we have to upstream a fair bit more first (net_util, cros_async, the vmm side of the vhost-user device). Since net_util isn't upstreamed yet, we've made some very minor changes to keep things consistent & building on Linux. TEST=unix is tested by bots upstream & downstream; windows is tested by bots downstream only. BUG=b:226233737 Change-Id: Ie3f9818ff93c9e0085a5434055f9dc71c6f6851c Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/3549854 Reviewed-by: Keiichi Watanabe Tested-by: kokoro Commit-Queue: Noah Gold --- Cargo.toml | 1 + devices/Cargo.toml | 1 + devices/src/virtio/vhost/user/device/mod.rs | 2 + devices/src/virtio/vhost/user/device/net.rs | 395 ++---------------- .../src/virtio/vhost/user/device/unix/net.rs | 373 +++++++++++++++++ .../virtio/vhost/user/device/windows/net.rs | 288 +++++++++++++ net_util/src/lib.rs | 77 ++-- src/linux/device_helpers.rs | 2 +- 8 files changed, 756 insertions(+), 383 deletions(-) create mode 100644 devices/src/virtio/vhost/user/device/unix/net.rs create mode 100644 devices/src/virtio/vhost/user/device/windows/net.rs diff --git a/Cargo.toml b/Cargo.toml index 442a5258cd..b77b0bd1ba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -139,6 +139,7 @@ linux-aarch64 = ["all-linux"] plugin = ["protos/plugin", "crosvm_plugin", "kvm", "kvm_sys", "protobuf"] plugin-render-server = [] power-monitor-powerd = ["arch/power-monitor-powerd"] +slirp = ["devices/slirp"] tpm = ["devices/tpm"] usb = ["devices/usb"] video-decoder = ["devices/video-decoder"] diff --git a/devices/Cargo.toml b/devices/Cargo.toml index b8bc96f21c..ebbc7f7e90 100644 --- a/devices/Cargo.toml +++ b/devices/Cargo.toml @@ -18,6 +18,7 @@ minigbm = ["rutabaga_gfx/minigbm"] x = ["gpu_display/x", "rutabaga_gfx/x"] virgl_renderer = ["gpu", "rutabaga_gfx/virgl_renderer"] gfxstream = ["gpu", "rutabaga_gfx/gfxstream"] +slirp = [] [dependencies] argh = "0.1.7" diff --git a/devices/src/virtio/vhost/user/device/mod.rs b/devices/src/virtio/vhost/user/device/mod.rs index 553e3f2b3d..78c5d8a140 100644 --- a/devices/src/virtio/vhost/user/device/mod.rs +++ b/devices/src/virtio/vhost/user/device/mod.rs @@ -22,6 +22,8 @@ pub use cras_snd::run_cras_snd_device; pub use fs::run_fs_device; #[cfg(feature = "gpu")] pub use gpu::run_gpu_device; + +#[cfg(any(all(windows, feature = "slirp"), not(windows)))] pub use net::run_net_device; pub use vsock::run_vsock_device; pub use wl::run_wl_device; diff --git a/devices/src/virtio/vhost/user/device/net.rs b/devices/src/virtio/vhost/user/device/net.rs index 6b36ac83ea..2b7a64f615 100644 --- a/devices/src/virtio/vhost/user/device/net.rs +++ b/devices/src/virtio/vhost/user/device/net.rs @@ -2,43 +2,44 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -use std::net::Ipv4Addr; -use std::str::FromStr; +#[cfg_attr(windows, path = "windows/net.rs")] +#[cfg_attr(not(windows), path = "unix/net.rs")] +mod net; + +// Only Windows exposes public symbols, but the module level use is used on both platforms. +#[allow(unused_imports)] +pub use net::*; + use std::sync::Arc; -use std::thread; use anyhow::{anyhow, bail, Context}; -use argh::FromArgs; -use base::{error, validate_raw_descriptor, warn, Event, RawDescriptor}; -use cros_async::{EventAsync, Executor, IoSourceExt}; +use base::{error, Event}; +use cros_async::{EventAsync, Executor, IntoAsync}; use data_model::DataInit; -use futures::future::{AbortHandle, Abortable}; -use hypervisor::ProtectionType; -use net_util::{MacAddress, Tap, TapT}; +use futures::future::AbortHandle; +use net_util::TapT; use once_cell::sync::OnceCell; use sync::Mutex; -use virtio_sys::virtio_net; use vm_memory::GuestMemory; -use vmm_vhost::message::{VhostUserProtocolFeatures, VhostUserVirtioFeatures}; +use vmm_vhost::message::VhostUserProtocolFeatures; use crate::virtio; -use crate::virtio::net::{ - build_config, process_ctrl, process_rx, process_tx, validate_and_configure_tap, - virtio_features_to_tap_offload, NetError, -}; -use crate::virtio::vhost::user::device::{ - handler::{DeviceRequestHandler, Doorbell, VhostUserBackend}, - vvu::pci::VvuPciDevice, -}; +use crate::virtio::net::{build_config, process_ctrl, process_tx, virtio_features_to_tap_offload}; +use crate::virtio::vhost::user::device::handler::{Doorbell, VhostUserBackend}; thread_local! { - static NET_EXECUTOR: OnceCell = OnceCell::new(); + pub(crate) static NET_EXECUTOR: OnceCell = OnceCell::new(); } -async fn run_tx_queue( +// TODO(b/188947559): Come up with better way to include these constants. Compiler errors happen +// if they are kept in the trait. +const MAX_QUEUE_NUM: usize = 3; /* rx, tx, ctrl */ +const MAX_VRING_LEN: u16 = 256; + +async fn run_tx_queue( mut queue: virtio::Queue, mem: GuestMemory, - mut tap: Tap, + mut tap: T, doorbell: Arc>, kick_evt: EventAsync, ) { @@ -52,39 +53,10 @@ async fn run_tx_queue( } } -async fn run_rx_queue( +async fn run_ctrl_queue( mut queue: virtio::Queue, mem: GuestMemory, - mut tap: Box>, - doorbell: Arc>, - kick_evt: EventAsync, -) { - loop { - if let Err(e) = tap.wait_readable().await { - error!("Failed to wait for tap device to become readable: {}", e); - break; - } - - match process_rx(&doorbell, &mut queue, &mem, tap.as_source_mut()) { - Ok(()) => {} - Err(NetError::RxDescriptorsExhausted) => { - if let Err(e) = kick_evt.next_val().await { - error!("Failed to read kick event for rx queue: {}", e); - break; - } - } - Err(e) => { - error!("Failed to process rx queue: {}", e); - break; - } - } - } -} - -async fn run_ctrl_queue( - mut queue: virtio::Queue, - mem: GuestMemory, - mut tap: Tap, + mut tap: T, doorbell: Arc>, kick_evt: EventAsync, acked_features: u64, @@ -110,110 +82,32 @@ async fn run_ctrl_queue( } } -struct TapConfig { - host_ip: Ipv4Addr, - netmask: Ipv4Addr, - mac: MacAddress, -} - -impl FromStr for TapConfig { - type Err = anyhow::Error; - - fn from_str(arg: &str) -> Result { - let args: Vec<&str> = arg.split(',').collect(); - if args.len() != 3 { - bail!("TAP config must consist of 3 parts but {}", args.len()); - } - - let host_ip: Ipv4Addr = args[0] - .parse() - .map_err(|e| anyhow!("invalid IP address: {}", e))?; - let netmask: Ipv4Addr = args[1] - .parse() - .map_err(|e| anyhow!("invalid net mask: {}", e))?; - let mac: MacAddress = args[2] - .parse() - .map_err(|e| anyhow!("invalid MAC address: {}", e))?; - - Ok(Self { - host_ip, - netmask, - mac, - }) - } -} - -struct NetBackend { - tap: Tap, +pub(crate) struct NetBackend { + tap: T, avail_features: u64, acked_features: u64, acked_protocol_features: VhostUserProtocolFeatures, - workers: [Option; Self::MAX_QUEUE_NUM], + workers: [Option; MAX_QUEUE_NUM], mtu: u16, + #[cfg(all(windows, feature = "slirp"))] + slirp_kill_event: Event, } -impl NetBackend { - pub fn new_from_config(config: &TapConfig) -> anyhow::Result { - // Create a tap device. - let tap = Tap::new(true /* vnet_hdr */, false /* multi_queue */) - .context("failed to create tap device")?; - tap.set_ip_addr(config.host_ip) - .context("failed to set IP address")?; - tap.set_netmask(config.netmask) - .context("failed to set netmask")?; - tap.set_mac_address(config.mac) - .context("failed to set MAC address")?; - - Self::new(tap) - } - - pub fn new_from_tap_fd(tap_fd: RawDescriptor) -> anyhow::Result { - let tap_fd = validate_raw_descriptor(tap_fd).context("failed to validate tap fd")?; - // Safe because we ensure that we get a unique handle to the fd. - let tap = - unsafe { Tap::from_raw_descriptor(tap_fd).context("failed to create tap device")? }; - - Self::new(tap) - } - - fn new(tap: Tap) -> anyhow::Result { - let vq_pairs = Self::max_vq_pairs(); - tap.enable().context("failed to enable tap")?; - validate_and_configure_tap(&tap, vq_pairs as u16) - .context("failed to validate and configure tap")?; - - let avail_features = virtio::base_features(ProtectionType::Unprotected) - | 1 << virtio_net::VIRTIO_NET_F_GUEST_CSUM - | 1 << virtio_net::VIRTIO_NET_F_CSUM - | 1 << virtio_net::VIRTIO_NET_F_CTRL_VQ - | 1 << virtio_net::VIRTIO_NET_F_CTRL_GUEST_OFFLOADS - | 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_MTU - | VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits(); - - let mtu = tap.mtu()?; - - Ok(Self { - tap, - avail_features, - acked_features: 0, - acked_protocol_features: VhostUserProtocolFeatures::empty(), - workers: Default::default(), - mtu, - }) - } - +impl NetBackend +where + T: TapT + IntoAsync, +{ fn max_vq_pairs() -> usize { Self::MAX_QUEUE_NUM / 2 } } -impl VhostUserBackend for NetBackend { - const MAX_QUEUE_NUM: usize = 3; /* rx, tx, ctrl */ - const MAX_VRING_LEN: u16 = 256; +impl VhostUserBackend for NetBackend +where + T: TapT + IntoAsync, +{ + const MAX_QUEUE_NUM: usize = MAX_QUEUE_NUM; /* rx, tx, ctrl */ + const MAX_VRING_LEN: u16 = MAX_VRING_LEN; type Error = anyhow::Error; @@ -266,67 +160,12 @@ impl VhostUserBackend for NetBackend { fn start_queue( &mut self, idx: usize, - mut queue: virtio::Queue, + queue: virtio::Queue, mem: GuestMemory, doorbell: Arc>, kick_evt: Event, ) -> anyhow::Result<()> { - if let Some(handle) = self.workers.get_mut(idx).and_then(Option::take) { - warn!("Starting new queue handler without stopping old handler"); - handle.abort(); - } - - // Enable any virtqueue features that were negotiated (like VIRTIO_RING_F_EVENT_IDX). - queue.ack_features(self.acked_features); - - NET_EXECUTOR.with(|ex| { - // Safe because the executor is initialized in main() below. - let ex = ex.get().expect("Executor not initialized"); - - let kick_evt = EventAsync::new(kick_evt.0, ex) - .context("failed to create EventAsync for kick_evt")?; - let tap = self.tap.try_clone().context("failed to clone tap device")?; - let (handle, registration) = AbortHandle::new_pair(); - match idx { - 0 => { - let tap = ex - .async_from(tap) - .context("failed to create async tap device")?; - - ex.spawn_local(Abortable::new( - run_rx_queue(queue, mem, tap, doorbell, kick_evt), - registration, - )) - .detach(); - } - 1 => { - ex.spawn_local(Abortable::new( - run_tx_queue(queue, mem, tap, doorbell, kick_evt), - registration, - )) - .detach(); - } - 2 => { - ex.spawn_local(Abortable::new( - run_ctrl_queue( - queue, - mem, - tap, - doorbell, - kick_evt, - self.acked_features, - 1, /* vq_pairs */ - ), - registration, - )) - .detach(); - } - _ => bail!("attempted to start unknown queue: {}", idx), - } - - self.workers[idx] = Some(handle); - Ok(()) - }) + net::start_queue(self, idx, queue, mem, doorbell, kick_evt) } fn stop_queue(&mut self, idx: usize) { @@ -336,155 +175,7 @@ impl VhostUserBackend for NetBackend { } } -#[derive(FromArgs)] -#[argh(description = "")] -struct Options { - #[argh( - option, - description = "TAP device config. (e.g. \ - \"/path/to/sock,10.0.2.2,255.255.255.0,12:34:56:78:9a:bc\")", - arg_name = "SOCKET_PATH,IP_ADDR,NET_MASK,MAC_ADDR" - )] - device: Vec, - #[argh( - option, - description = "TAP FD with a socket path", - arg_name = "SOCKET_PATH,TAP_FD" - )] - tap_fd: Vec, - #[argh( - option, - description = "TAP device config for virtio-vhost-user. \ - (e.g. \"0000:00:07.0,10.0.2.2,255.255.255.0,12:34:56:78:9a:bc\")", - arg_name = "DEVICE,IP_ADDR,NET_MASK,MAC_ADDR" - )] - vvu_device: Vec, - #[argh( - option, - description = "TAP FD with a vfio device name for virtio-vhost-user", - arg_name = "DEVICE,TAP_FD" - )] - vvu_tap_fd: Vec, -} - -enum Connection { - Socket(String), - Vfio(String), -} - -fn new_backend_from_device_arg(arg: &str) -> anyhow::Result<(String, NetBackend)> { - let pos = match arg.find(',') { - Some(p) => p, - None => { - bail!("device must take comma-separated argument"); - } - }; - let conn = &arg[0..pos]; - let cfg = &arg[pos + 1..] - .parse::() - .context("failed to parse tap config")?; - let backend = NetBackend::new_from_config(cfg).context("failed to create NetBackend")?; - Ok((conn.to_string(), backend)) -} - -fn new_backend_from_tapfd_arg(arg: &str) -> anyhow::Result<(String, NetBackend)> { - let pos = match arg.find(',') { - Some(p) => p, - None => { - bail!("'tap-fd' flag must take comma-separated argument"); - } - }; - let conn = &arg[0..pos]; - let tap_fd = &arg[pos + 1..] - .parse::() - .context("failed to parse tap-fd")?; - let backend = NetBackend::new_from_tap_fd(*tap_fd).context("failed to create NetBackend")?; - - Ok((conn.to_string(), backend)) -} - /// Starts a vhost-user net device. -/// Returns an error if the given `args` is invalid or the device fails to run. pub fn run_net_device(program_name: &str, args: &[&str]) -> anyhow::Result<()> { - let opts = match Options::from_args(&[program_name], args) { - Ok(opts) => opts, - Err(e) => { - if e.status.is_err() { - bail!(e.output); - } else { - println!("{}", e.output); - } - return Ok(()); - } - }; - - let num_devices = - opts.device.len() + opts.tap_fd.len() + opts.vvu_device.len() + opts.vvu_tap_fd.len(); - - if num_devices == 0 { - bail!("no device option was passed"); - } - - let mut devices: Vec<(Connection, NetBackend)> = Vec::with_capacity(num_devices); - - // vhost-user - for arg in opts.device.iter() { - devices.push( - new_backend_from_device_arg(arg) - .map(|(s, backend)| (Connection::Socket(s), backend))?, - ); - } - for arg in opts.tap_fd.iter() { - devices.push( - new_backend_from_tapfd_arg(arg).map(|(s, backend)| (Connection::Socket(s), backend))?, - ); - } - - // virtio-vhost-user - for arg in opts.vvu_device.iter() { - devices.push( - new_backend_from_device_arg(arg).map(|(s, backend)| (Connection::Vfio(s), backend))?, - ); - } - for arg in opts.vvu_tap_fd.iter() { - devices.push( - new_backend_from_tapfd_arg(arg).map(|(s, backend)| (Connection::Vfio(s), backend))?, - ); - } - - let mut threads = Vec::with_capacity(num_devices); - - for (conn, backend) in devices { - match conn { - Connection::Socket(socket) => { - let ex = Executor::new().context("failed to create executor")?; - let handler = DeviceRequestHandler::new(backend); - threads.push(thread::spawn(move || { - NET_EXECUTOR.with(|thread_ex| { - let _ = thread_ex.set(ex.clone()); - }); - ex.run_until(handler.run(&socket, &ex))? - })); - } - Connection::Vfio(device_name) => { - let device = VvuPciDevice::new(device_name.as_str(), NetBackend::MAX_QUEUE_NUM)?; - let handler = DeviceRequestHandler::new(backend); - let ex = Executor::new().context("failed to create executor")?; - threads.push(thread::spawn(move || { - NET_EXECUTOR.with(|thread_ex| { - let _ = thread_ex.set(ex.clone()); - }); - ex.run_until(handler.run_vvu(device, &ex))? - })); - } - }; - } - - for t in threads { - match t.join() { - Ok(r) => r?, - Err(e) => bail!("thread panicked: {:?}", e), - } - } - Ok(()) + start_device(program_name, args) } diff --git a/devices/src/virtio/vhost/user/device/unix/net.rs b/devices/src/virtio/vhost/user/device/unix/net.rs new file mode 100644 index 0000000000..aca8573c95 --- /dev/null +++ b/devices/src/virtio/vhost/user/device/unix/net.rs @@ -0,0 +1,373 @@ +// Copyright 2022 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::net::Ipv4Addr; +use std::str::FromStr; +use std::sync::Arc; +use std::thread; + +use anyhow::{anyhow, bail, Context}; +use argh::FromArgs; +use base::validate_raw_descriptor; +use base::{error, warn, Event, RawDescriptor}; +use cros_async::{EventAsync, Executor, IntoAsync, IoSourceExt}; +use futures::future::{AbortHandle, Abortable}; +use hypervisor::ProtectionType; +use net_util::TapT; +use net_util::{MacAddress, Tap}; +use sync::Mutex; +use virtio_sys::virtio_net; +use vm_memory::GuestMemory; +use vmm_vhost::message::{VhostUserProtocolFeatures, VhostUserVirtioFeatures}; + +use super::{run_ctrl_queue, run_tx_queue, NetBackend, NET_EXECUTOR}; +use crate::virtio; +use crate::virtio::net::validate_and_configure_tap; +use crate::virtio::net::{process_rx, NetError}; +use crate::virtio::vhost::user::device::handler::{ + DeviceRequestHandler, Doorbell, VhostUserBackend, +}; +use virtio::vhost::user::device::vvu::pci::VvuPciDevice; + +struct TapConfig { + host_ip: Ipv4Addr, + netmask: Ipv4Addr, + mac: MacAddress, +} + +impl FromStr for TapConfig { + type Err = anyhow::Error; + + fn from_str(arg: &str) -> Result { + let args: Vec<&str> = arg.split(',').collect(); + if args.len() != 3 { + bail!("TAP config must consist of 3 parts but {}", args.len()); + } + + let host_ip: Ipv4Addr = args[0] + .parse() + .map_err(|e| anyhow!("invalid IP address: {}", e))?; + let netmask: Ipv4Addr = args[1] + .parse() + .map_err(|e| anyhow!("invalid net mask: {}", e))?; + let mac: MacAddress = args[2] + .parse() + .map_err(|e| anyhow!("invalid MAC address: {}", e))?; + + Ok(Self { + host_ip, + netmask, + mac, + }) + } +} + +impl NetBackend +where + T: TapT + IntoAsync, +{ + fn new_from_config(config: &TapConfig) -> anyhow::Result { + // Create a tap device. + let tap = T::new(true /* vnet_hdr */, false /* multi_queue */) + .context("failed to create tap device")?; + tap.set_ip_addr(config.host_ip) + .context("failed to set IP address")?; + tap.set_netmask(config.netmask) + .context("failed to set netmask")?; + tap.set_mac_address(config.mac) + .context("failed to set MAC address")?; + + Self::new(tap) + } + + pub fn new_from_tap_fd(tap_fd: RawDescriptor) -> anyhow::Result { + let tap_fd = validate_raw_descriptor(tap_fd).context("failed to validate tap fd")?; + // Safe because we ensure that we get a unique handle to the fd. + let tap = unsafe { T::from_raw_descriptor(tap_fd).context("failed to create tap device")? }; + + Self::new(tap) + } + + fn new(tap: T) -> anyhow::Result { + let vq_pairs = Self::max_vq_pairs(); + validate_and_configure_tap(&tap, vq_pairs as u16) + .context("failed to validate and configure tap")?; + + let avail_features = virtio::base_features(ProtectionType::Unprotected) + | 1 << virtio_net::VIRTIO_NET_F_GUEST_CSUM + | 1 << virtio_net::VIRTIO_NET_F_CSUM + | 1 << virtio_net::VIRTIO_NET_F_CTRL_VQ + | 1 << virtio_net::VIRTIO_NET_F_CTRL_GUEST_OFFLOADS + | 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_MTU + | VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits(); + + let mtu = tap.mtu()?; + + Ok(Self { + tap, + avail_features, + acked_features: 0, + acked_protocol_features: VhostUserProtocolFeatures::empty(), + workers: Default::default(), + mtu, + }) + } +} + +async fn run_rx_queue( + mut queue: virtio::Queue, + mem: GuestMemory, + mut tap: Box>, + doorbell: Arc>, + kick_evt: EventAsync, +) { + loop { + if let Err(e) = tap.wait_readable().await { + error!("Failed to wait for tap device to become readable: {}", e); + break; + } + match process_rx(&doorbell, &mut queue, &mem, tap.as_source_mut()) { + Ok(()) => {} + Err(NetError::RxDescriptorsExhausted) => { + if let Err(e) = kick_evt.next_val().await { + error!("Failed to read kick event for rx queue: {}", e); + break; + } + } + Err(e) => { + error!("Failed to process rx queue: {}", e); + break; + } + } + } +} + +/// Platform specific impl of VhostUserBackend::start_queue. +pub(super) fn start_queue( + backend: &mut NetBackend, + idx: usize, + mut queue: virtio::Queue, + mem: GuestMemory, + doorbell: Arc>, + kick_evt: Event, +) -> anyhow::Result<()> { + if let Some(handle) = backend.workers.get_mut(idx).and_then(Option::take) { + warn!("Starting new queue handler without stopping old handler"); + handle.abort(); + } + + // Enable any virtqueue features that were negotiated (like VIRTIO_RING_F_EVENT_IDX). + queue.ack_features(backend.acked_features); + + NET_EXECUTOR.with(|ex| { + // Safe because the executor is initialized in main() below. + let ex = ex.get().expect("Executor not initialized"); + + let kick_evt = + EventAsync::new(kick_evt.0, ex).context("failed to create EventAsync for kick_evt")?; + let tap = backend + .tap + .try_clone() + .context("failed to clone tap device")?; + let (handle, registration) = AbortHandle::new_pair(); + match idx { + 0 => { + let tap = ex + .async_from(tap) + .context("failed to create async tap device")?; + + ex.spawn_local(Abortable::new( + run_rx_queue(queue, mem, tap, doorbell, kick_evt), + registration, + )) + .detach(); + } + 1 => { + ex.spawn_local(Abortable::new( + run_tx_queue(queue, mem, tap, doorbell, kick_evt), + registration, + )) + .detach(); + } + 2 => { + ex.spawn_local(Abortable::new( + run_ctrl_queue( + queue, + mem, + tap, + doorbell, + kick_evt, + backend.acked_features, + 1, /* vq_pairs */ + ), + registration, + )) + .detach(); + } + _ => bail!("attempted to start unknown queue: {}", idx), + } + + backend.workers[idx] = Some(handle); + Ok(()) + }) +} + +#[derive(FromArgs)] +#[argh(description = "")] +struct Options { + #[argh( + option, + description = "TAP device config. (e.g. \ + \"/path/to/sock,10.0.2.2,255.255.255.0,12:34:56:78:9a:bc\")", + arg_name = "SOCKET_PATH,IP_ADDR,NET_MASK,MAC_ADDR" + )] + device: Vec, + #[argh( + option, + description = "TAP FD with a socket path", + arg_name = "SOCKET_PATH,TAP_FD" + )] + tap_fd: Vec, + #[argh( + option, + description = "TAP device config for virtio-vhost-user. \ + (e.g. \"0000:00:07.0,10.0.2.2,255.255.255.0,12:34:56:78:9a:bc\")", + arg_name = "DEVICE,IP_ADDR,NET_MASK,MAC_ADDR" + )] + vvu_device: Vec, + #[argh( + option, + description = "TAP FD with a vfio device name for virtio-vhost-user", + arg_name = "DEVICE,TAP_FD" + )] + vvu_tap_fd: Vec, +} + +enum Connection { + Socket(String), + Vfio(String), +} + +fn new_backend_from_device_arg(arg: &str) -> anyhow::Result<(String, NetBackend)> { + let pos = match arg.find(',') { + Some(p) => p, + None => { + bail!("device must take comma-separated argument"); + } + }; + let conn = &arg[0..pos]; + let cfg = &arg[pos + 1..] + .parse::() + .context("failed to parse tap config")?; + let backend = NetBackend::::new_from_config(cfg).context("failed to create NetBackend")?; + Ok((conn.to_string(), backend)) +} + +fn new_backend_from_tapfd_arg(arg: &str) -> anyhow::Result<(String, NetBackend)> { + let pos = match arg.find(',') { + Some(p) => p, + None => { + bail!("'tap-fd' flag must take comma-separated argument"); + } + }; + let conn = &arg[0..pos]; + let tap_fd = &arg[pos + 1..] + .parse::() + .context("failed to parse tap-fd")?; + let backend = + NetBackend::::new_from_tap_fd(*tap_fd).context("failed to create NetBackend")?; + + Ok((conn.to_string(), backend)) +} + +/// Starts a vhost-user net device. +/// Returns an error if the given `args` is invalid or the device fails to run. +pub(crate) fn start_device(program_name: &str, args: &[&str]) -> anyhow::Result<()> { + let opts = match Options::from_args(&[program_name], args) { + Ok(opts) => opts, + Err(e) => { + if e.status.is_err() { + bail!(e.output); + } else { + println!("{}", e.output); + } + return Ok(()); + } + }; + + let num_devices = + opts.device.len() + opts.tap_fd.len() + opts.vvu_device.len() + opts.vvu_tap_fd.len(); + + if num_devices == 0 { + bail!("no device option was passed"); + } + + let mut devices: Vec<(Connection, NetBackend)> = Vec::with_capacity(num_devices); + + // vhost-user + for arg in opts.device.iter() { + devices.push( + new_backend_from_device_arg(arg) + .map(|(s, backend)| (Connection::Socket(s), backend))?, + ); + } + for arg in opts.tap_fd.iter() { + devices.push( + new_backend_from_tapfd_arg(arg).map(|(s, backend)| (Connection::Socket(s), backend))?, + ); + } + + // virtio-vhost-user + for arg in opts.vvu_device.iter() { + devices.push( + new_backend_from_device_arg(arg).map(|(s, backend)| (Connection::Vfio(s), backend))?, + ); + } + for arg in opts.vvu_tap_fd.iter() { + devices.push( + new_backend_from_tapfd_arg(arg).map(|(s, backend)| (Connection::Vfio(s), backend))?, + ); + } + + let mut threads = Vec::with_capacity(num_devices); + + for (conn, backend) in devices { + match conn { + Connection::Socket(socket) => { + let ex = Executor::new().context("failed to create executor")?; + let handler = DeviceRequestHandler::new(backend); + threads.push(thread::spawn(move || { + NET_EXECUTOR.with(|thread_ex| { + let _ = thread_ex.set(ex.clone()); + }); + ex.run_until(handler.run(&socket, &ex))? + })); + } + Connection::Vfio(device_name) => { + let device = + VvuPciDevice::new(device_name.as_str(), NetBackend::::MAX_QUEUE_NUM)?; + let handler = DeviceRequestHandler::new(backend); + let ex = Executor::new().context("failed to create executor")?; + threads.push(thread::spawn(move || { + NET_EXECUTOR.with(|thread_ex| { + let _ = thread_ex.set(ex.clone()); + }); + ex.run_until(handler.run_vvu(device, &ex))? + })); + } + }; + } + + for t in threads { + match t.join() { + Ok(r) => r?, + Err(e) => bail!("thread panicked: {:?}", e), + } + } + Ok(()) +} diff --git a/devices/src/virtio/vhost/user/device/windows/net.rs b/devices/src/virtio/vhost/user/device/windows/net.rs new file mode 100644 index 0000000000..67b5395cbc --- /dev/null +++ b/devices/src/virtio/vhost/user/device/windows/net.rs @@ -0,0 +1,288 @@ +// Copyright 2022 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 anyhow::{bail, Context}; +use argh::FromArgs; +use base::named_pipes::{OverlappedWrapper, PipeConnection}; +use base::{error, warn, Event, RawDescriptor}; +use cros_async::{EventAsync, Executor, IntoAsync, IoSourceExt}; +use futures::future::{AbortHandle, Abortable}; +use hypervisor::ProtectionType; +#[cfg(feature = "slirp")] +use net_util::Slirp; +use net_util::TapT; +#[cfg(feature = "slirp")] +use serde::{Deserialize, Serialize}; +use std::sync::Arc; +use sync::Mutex; +use virtio_sys::virtio_net; +use vm_memory::GuestMemory; +use vmm_vhost::message::{VhostUserProtocolFeatures, VhostUserVirtioFeatures}; + +use super::{run_ctrl_queue, run_tx_queue, NetBackend, NET_EXECUTOR}; +use crate::virtio; +#[cfg(feature = "slirp")] +use crate::virtio::net::MAX_BUFFER_SIZE; +use crate::virtio::net::{process_rx, NetError}; +use crate::virtio::vhost::user::device::handler::read_from_tube_transporter; +use crate::virtio::vhost::user::device::handler::{DeviceRequestHandler, Doorbell}; +use crate::virtio::{base_features, SignalableInterrupt}; +use broker_ipc::{common_child_setup, CommonChildStartupArgs}; +use tube_transporter::TubeToken; + +impl NetBackend +where + T: TapT + IntoAsync, +{ + #[cfg(feature = "slirp")] + pub fn new_slirp( + guest_pipe: PipeConnection, + slirp_kill_event: Event, + ) -> anyhow::Result> { + let avail_features = base_features(ProtectionType::Unprotected) + | 1 << virtio_net::VIRTIO_NET_F_CTRL_VQ + | VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits(); + let slirp = Slirp::new_for_multi_process(guest_pipe).map_err(NetError::SlirpCreateError)?; + + Ok(NetBackend:: { + tap: slirp, + avail_features, + acked_features: 0, + acked_protocol_features: VhostUserProtocolFeatures::empty(), + workers: Default::default(), + mtu: 1500, + slirp_kill_event, + }) + } +} + +async fn run_rx_queue( + mut queue: virtio::Queue, + mem: GuestMemory, + mut tap: Box>, + call_evt: Arc>, + kick_evt: EventAsync, + read_notifier: EventAsync, + mut overlapped_wrapper: OverlappedWrapper, +) { + let mut rx_buf = [0u8; MAX_BUFFER_SIZE]; + let mut rx_count = 0; + let mut deferred_rx = false; + tap.as_source_mut() + .read_overlapped(&mut rx_buf, &mut overlapped_wrapper) + .expect("read_overlapped failed"); + loop { + // If we already have a packet from deferred RX, we don't need to wait for the slirp device. + if !deferred_rx { + if let Err(e) = read_notifier.next_val().await { + error!("Failed to wait for tap device to become readable: {}", e); + break; + } + } + + let needs_interrupt = process_rx( + &call_evt, + &mut queue, + &mem, + tap.as_source_mut(), + &mut rx_buf, + &mut deferred_rx, + &mut rx_count, + &mut overlapped_wrapper, + ); + if needs_interrupt { + call_evt.lock().signal_used_queue(queue.vector); + } + + // There aren't any RX descriptors available for us to write packets to. Wait for the guest + // to consume some packets and make more descriptors available to us. + if deferred_rx { + if let Err(e) = kick_evt.next_val().await { + error!("Failed to read kick event for rx queue: {}", e); + break; + } + } + } +} + +/// Platform specific impl of VhostUserBackend::start_queue. +pub(super) fn start_queue( + backend: &mut NetBackend, + idx: usize, + mut queue: virtio::Queue, + mem: GuestMemory, + doorbell: Arc>, + kick_evt: Event, +) -> anyhow::Result<()> { + if let Some(handle) = backend.workers.get_mut(idx).and_then(Option::take) { + warn!("Starting new queue handler without stopping old handler"); + handle.abort(); + } + + // Enable any virtqueue features that were negotiated (like VIRTIO_RING_F_EVENT_IDX). + queue.ack_features(backend.acked_features); + + let overlapped_wrapper = + OverlappedWrapper::new(true).expect("Failed to create overlapped wrapper"); + + super::NET_EXECUTOR.with(|ex| { + // Safe because the executor is initialized in main() below. + let ex = ex.get().expect("Executor not initialized"); + + let kick_evt = + EventAsync::new(kick_evt.0, ex).context("failed to create EventAsync for kick_evt")?; + let tap = backend + .tap + .try_clone() + .context("failed to clone tap device")?; + let (handle, registration) = AbortHandle::new_pair(); + match idx { + 0 => { + let tap = ex + .async_from(tap) + .context("failed to create async tap device")?; + let read_notifier = overlapped_wrapper + .get_h_event_ref() + .unwrap() + .try_clone() + .unwrap(); + let read_notifier = EventAsync::new_without_reset(read_notifier, &ex) + .context("failed to create async read notifier")?; + + ex.spawn_local(Abortable::new( + run_rx_queue( + queue, + mem, + tap, + doorbell, + kick_evt, + read_notifier, + overlapped_wrapper, + ), + registration, + )) + .detach(); + } + 1 => { + ex.spawn_local(Abortable::new( + run_tx_queue(queue, mem, tap, doorbell, kick_evt), + registration, + )) + .detach(); + } + 2 => { + ex.spawn_local(Abortable::new( + run_ctrl_queue( + queue, + mem, + tap, + doorbell, + kick_evt, + backend.acked_features, + 1, /* vq_pairs */ + ), + registration, + )) + .detach(); + } + _ => bail!("attempted to start unknown queue: {}", idx), + } + + backend.workers[idx] = Some(handle); + Ok(()) + }) +} + +#[cfg(feature = "slirp")] +impl Drop for NetBackend +where + T: TapT + IntoAsync, +{ + fn drop(&mut self) { + let _ = self.slirp_kill_event.write(1); + } +} + +/// Config arguments passed through the bootstrap Tube from the broker to the Net backend +/// process. +#[cfg(feature = "slirp")] +#[derive(Serialize, Deserialize, Debug)] +pub struct NetBackendConfig { + pub guest_pipe: PipeConnection, + pub slirp_kill_event: Event, +} + +#[derive(FromArgs)] +#[argh(description = "")] +struct Options { + #[argh( + option, + description = "pipe handle end for Tube Transporter", + arg_name = "HANDLE" + )] + bootstrap: usize, +} + +#[cfg(all(windows, not(feature = "slirp")))] +compile_error!("vhost-user net device requires slirp feature on Windows."); + +#[cfg(feature = "slirp")] +pub fn run_device(program_name: &str, args: &[&str]) -> anyhow::Result<()> { + let opts = match Options::from_args(&[program_name], args) { + Ok(opts) => opts, + Err(e) => { + if e.status.is_err() { + bail!(e.output); + } else { + println!("{}", e.output); + } + return Ok(()); + } + }; + + // Get the Tubes from the TubeTransporter. Then get the "Config" from the bootstrap_tube + // which will contain slirp settings. + let raw_transport_tube = opts.bootstrap as RawDescriptor; + + let mut tubes = read_from_tube_transporter(raw_transport_tube).unwrap(); + + let vhost_user_tube = tubes.get_tube(TubeToken::VhostUser).unwrap(); + let bootstrap_tube = tubes.get_tube(TubeToken::Bootstrap).unwrap(); + + let startup_args: CommonChildStartupArgs = + bootstrap_tube.recv::().unwrap(); + common_child_setup(startup_args).unwrap(); + + let net_backend_config = bootstrap_tube.recv::().unwrap(); + + let exit_event = bootstrap_tube.recv::()?; + + // We only have one net device for now. + let dev = NetBackend::::new_slirp( + net_backend_config.guest_pipe, + net_backend_config.slirp_kill_event, + ) + .unwrap(); + + let handler = DeviceRequestHandler::new(dev); + + let ex = Executor::new().context("failed to create executor")?; + + NET_EXECUTOR.with(|net_ex| { + let _ = net_ex.set(ex.clone()); + }); + + if sandbox::is_sandbox_target() { + sandbox::TargetServices::get() + .expect("failed to get target services") + .unwrap() + .lower_token(); + } + + if let Err(e) = ex.run_until(handler.run(vhost_user_tube, exit_event, &ex)) { + bail!("error occurred: {}", e); + } + + Ok(()) +} diff --git a/net_util/src/lib.rs b/net_util/src/lib.rs index 3726c1c4d1..42e4658985 100644 --- a/net_util/src/lib.rs +++ b/net_util/src/lib.rs @@ -165,24 +165,6 @@ pub struct Tap { } impl Tap { - pub unsafe fn from_raw_descriptor(fd: RawDescriptor) -> Result { - let tap_file = File::from_raw_descriptor(fd); - - // Get the interface name since we will need it for some ioctls. - let mut ifreq: net_sys::ifreq = Default::default(); - let ret = ioctl_with_mut_ref(&tap_file, net_sys::TUNGETIFF(), &mut ifreq); - - if ret < 0 { - return Err(Error::IoctlError(SysError::last())); - } - - Ok(Tap { - tap_file, - if_name: ifreq.ifr_ifrn.ifrn_name, - if_flags: ifreq.ifr_ifru.ifru_flags, - }) - } - pub fn create_tap_with_ifreq(ifreq: &mut net_sys::ifreq) -> Result { // Open calls are safe because we give a constant nul-terminated // string and verify the result. @@ -218,18 +200,6 @@ impl Tap { if_flags: unsafe { ifreq.ifr_ifru.ifru_flags }, }) } - - pub fn try_clone(&self) -> Result { - self.tap_file - .try_clone() - .map(|tap_file| Tap { - tap_file, - if_name: self.if_name, - if_flags: self.if_flags, - }) - .map_err(SysError::from) - .map_err(Error::CloneTap) - } } pub trait TapT: FileReadWriteVolatile + Read + Write + AsRawDescriptor + Send + Sized { @@ -293,6 +263,12 @@ pub trait TapT: FileReadWriteVolatile + Read + Write + AsRawDescriptor + Send + /// Get the interface flags fn if_flags(&self) -> i32; + + /// Try to clone + fn try_clone(&self) -> Result; + + /// Convert raw descriptor to TapT. + unsafe fn from_raw_descriptor(descriptor: RawDescriptor) -> Result; } impl TapT for Tap { @@ -551,6 +527,38 @@ impl TapT for Tap { fn if_flags(&self) -> i32 { self.if_flags.into() } + + fn try_clone(&self) -> Result { + self.tap_file + .try_clone() + .map(|tap_file| Tap { + tap_file, + if_name: self.if_name, + if_flags: self.if_flags, + }) + .map_err(SysError::from) + .map_err(Error::CloneTap) + } + + /// # Safety: fd is a valid FD and ownership of it is transferred when + /// calling this function. + unsafe fn from_raw_descriptor(fd: RawDescriptor) -> Result { + let tap_file = File::from_raw_descriptor(fd); + + // Get the interface name since we will need it for some ioctls. + let mut ifreq: net_sys::ifreq = Default::default(); + let ret = ioctl_with_mut_ref(&tap_file, net_sys::TUNGETIFF(), &mut ifreq); + + if ret < 0 { + return Err(Error::IoctlError(SysError::last())); + } + + Ok(Tap { + tap_file, + if_name: ifreq.ifr_ifrn.ifrn_name, + if_flags: ifreq.ifr_ifru.ifru_flags, + }) + } } impl Read for Tap { @@ -664,6 +672,15 @@ pub mod fakes { fn if_flags(&self) -> i32 { libc::IFF_TAP } + + fn try_clone(&self) -> Result { + unimplemented!() + } + + /// # Safety: panics on call / does nothing. + unsafe fn from_raw_descriptor(_descriptor: RawDescriptor) -> Result { + unimplemented!() + } } impl Drop for FakeTap { diff --git a/src/linux/device_helpers.rs b/src/linux/device_helpers.rs index bb9278d6cb..8f1e132013 100644 --- a/src/linux/device_helpers.rs +++ b/src/linux/device_helpers.rs @@ -47,7 +47,7 @@ use devices::{ }; use hypervisor::Vm; use minijail::{self, Minijail}; -use net_util::{MacAddress, Tap}; +use net_util::{MacAddress, Tap, TapT}; use resources::{Alloc, MmioType, SystemAllocator}; use sync::Mutex; use vm_memory::GuestAddress;