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 <keiichiw@chromium.org>
Tested-by: kokoro <noreply+kokoro@google.com>
Commit-Queue: Noah Gold <nkgold@google.com>
This commit is contained in:
Noah Gold 2022-03-24 11:16:33 -07:00 committed by Chromeos LUCI
parent cd0e7edcc7
commit 44306221c1
8 changed files with 756 additions and 383 deletions

View file

@ -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"]

View file

@ -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"

View file

@ -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;

View file

@ -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<Executor> = OnceCell::new();
pub(crate) static NET_EXECUTOR: OnceCell<Executor> = 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<T: TapT>(
mut queue: virtio::Queue,
mem: GuestMemory,
mut tap: Tap,
mut tap: T,
doorbell: Arc<Mutex<Doorbell>>,
kick_evt: EventAsync,
) {
@ -52,39 +53,10 @@ async fn run_tx_queue(
}
}
async fn run_rx_queue(
async fn run_ctrl_queue<T: TapT>(
mut queue: virtio::Queue,
mem: GuestMemory,
mut tap: Box<dyn IoSourceExt<Tap>>,
doorbell: Arc<Mutex<Doorbell>>,
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<Mutex<Doorbell>>,
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<Self, Self::Err> {
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<T: TapT + IntoAsync> {
tap: T,
avail_features: u64,
acked_features: u64,
acked_protocol_features: VhostUserProtocolFeatures,
workers: [Option<AbortHandle>; Self::MAX_QUEUE_NUM],
workers: [Option<AbortHandle>; 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<Self> {
// 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<Self> {
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<Self> {
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<T: 'static> NetBackend<T>
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<T: 'static> VhostUserBackend for NetBackend<T>
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<Mutex<Doorbell>>,
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<String>,
#[argh(
option,
description = "TAP FD with a socket path",
arg_name = "SOCKET_PATH,TAP_FD"
)]
tap_fd: Vec<String>,
#[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<String>,
#[argh(
option,
description = "TAP FD with a vfio device name for virtio-vhost-user",
arg_name = "DEVICE,TAP_FD"
)]
vvu_tap_fd: Vec<String>,
}
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::<TapConfig>()
.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::<i32>()
.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)
}

View file

@ -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<Self, Self::Err> {
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<T: 'static> NetBackend<T>
where
T: TapT + IntoAsync,
{
fn new_from_config(config: &TapConfig) -> anyhow::Result<Self> {
// 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<Self> {
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<Self> {
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<T: TapT>(
mut queue: virtio::Queue,
mem: GuestMemory,
mut tap: Box<dyn IoSourceExt<T>>,
doorbell: Arc<Mutex<Doorbell>>,
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<T: 'static + IntoAsync + TapT>(
backend: &mut NetBackend<T>,
idx: usize,
mut queue: virtio::Queue,
mem: GuestMemory,
doorbell: Arc<Mutex<Doorbell>>,
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<String>,
#[argh(
option,
description = "TAP FD with a socket path",
arg_name = "SOCKET_PATH,TAP_FD"
)]
tap_fd: Vec<String>,
#[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<String>,
#[argh(
option,
description = "TAP FD with a vfio device name for virtio-vhost-user",
arg_name = "DEVICE,TAP_FD"
)]
vvu_tap_fd: Vec<String>,
}
enum Connection {
Socket(String),
Vfio(String),
}
fn new_backend_from_device_arg(arg: &str) -> anyhow::Result<(String, NetBackend<Tap>)> {
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::<TapConfig>()
.context("failed to parse tap config")?;
let backend = NetBackend::<Tap>::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<Tap>)> {
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::<i32>()
.context("failed to parse tap-fd")?;
let backend =
NetBackend::<Tap>::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<Tap>)> = 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::<Tap>::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(())
}

View file

@ -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<T: 'static> NetBackend<T>
where
T: TapT + IntoAsync,
{
#[cfg(feature = "slirp")]
pub fn new_slirp(
guest_pipe: PipeConnection,
slirp_kill_event: Event,
) -> anyhow::Result<NetBackend<Slirp>> {
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::<Slirp> {
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<T: TapT>(
mut queue: virtio::Queue,
mem: GuestMemory,
mut tap: Box<dyn IoSourceExt<T>>,
call_evt: Arc<Mutex<Doorbell>>,
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<T: 'static + IntoAsync + TapT>(
backend: &mut NetBackend<T>,
idx: usize,
mut queue: virtio::Queue,
mem: GuestMemory,
doorbell: Arc<Mutex<Doorbell>>,
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<T> Drop for NetBackend<T>
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::<CommonChildStartupArgs>().unwrap();
common_child_setup(startup_args).unwrap();
let net_backend_config = bootstrap_tube.recv::<NetBackendConfig>().unwrap();
let exit_event = bootstrap_tube.recv::<Event>()?;
// We only have one net device for now.
let dev = NetBackend::<net_util::Slirp>::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(())
}

View file

@ -165,24 +165,6 @@ pub struct Tap {
}
impl Tap {
pub unsafe fn from_raw_descriptor(fd: RawDescriptor) -> Result<Tap> {
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<Tap> {
// 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<Tap> {
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<Self>;
/// Convert raw descriptor to TapT.
unsafe fn from_raw_descriptor(descriptor: RawDescriptor) -> Result<Self>;
}
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<Tap> {
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<Tap> {
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<Self> {
unimplemented!()
}
/// # Safety: panics on call / does nothing.
unsafe fn from_raw_descriptor(_descriptor: RawDescriptor) -> Result<Self> {
unimplemented!()
}
}
impl Drop for FakeTap {

View file

@ -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;