blob: e24cf5e1fbd265853add8c0aa95793dac9c27492 [file] [log] [blame]
// Copyright 2022 The ChromiumOS Authors
// 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;
use anyhow::bail;
use anyhow::Context;
use argh::FromArgs;
use base::error;
use base::info;
use base::validate_raw_descriptor;
use base::warn;
use base::Event;
use base::RawDescriptor;
use cros_async::EventAsync;
use cros_async::Executor;
use cros_async::IntoAsync;
use cros_async::IoSource;
use futures::future::AbortHandle;
use futures::future::Abortable;
use hypervisor::ProtectionType;
use net_util::sys::unix::Tap;
use net_util::MacAddress;
use net_util::TapT;
use sync::Mutex;
use virtio_sys::virtio_net;
use vm_memory::GuestMemory;
use vmm_vhost::message::VhostUserProtocolFeatures;
use vmm_vhost::message::VhostUserVirtioFeatures;
use crate::virtio;
use crate::virtio::net::process_rx;
use crate::virtio::net::validate_and_configure_tap;
use crate::virtio::net::NetError;
use crate::virtio::vhost::user::device::handler::sys::Doorbell;
use crate::virtio::vhost::user::device::handler::VhostUserBackend;
use crate::virtio::vhost::user::device::handler::WorkerState;
use crate::virtio::vhost::user::device::listener::sys::VhostUserListener;
use crate::virtio::vhost::user::device::listener::VhostUserListenerTrait;
use crate::virtio::vhost::user::device::net::run_ctrl_queue;
use crate::virtio::vhost::user::device::net::run_tx_queue;
use crate::virtio::vhost::user::device::net::NetBackend;
use crate::virtio::vhost::user::device::net::NET_EXECUTOR;
use crate::PciAddress;
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>(
queue: Arc<Mutex<virtio::Queue>>,
mem: GuestMemory,
mut tap: IoSource<T>,
doorbell: 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, &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(in crate::virtio::vhost::user::device::net) fn start_queue<T: 'static + IntoAsync + TapT>(
backend: &mut NetBackend<T>,
idx: usize,
queue: virtio::Queue,
mem: GuestMemory,
doorbell: Doorbell,
kick_evt: Event,
) -> anyhow::Result<()> {
if backend.workers[idx].is_some() {
warn!("Starting new queue handler without stopping old handler");
backend.stop_queue(idx)?;
}
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, 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();
let queue = Arc::new(Mutex::new(queue));
let queue_task = 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.clone(), mem, tap, doorbell, kick_evt),
registration,
))
}
1 => ex.spawn_local(Abortable::new(
run_tx_queue(queue.clone(), mem, tap, doorbell, kick_evt),
registration,
)),
2 => {
ex.spawn_local(Abortable::new(
run_ctrl_queue(
queue.clone(),
mem,
tap,
doorbell,
kick_evt,
backend.acked_features,
1, /* vq_pairs */
),
registration,
))
}
_ => bail!("attempted to start unknown queue: {}", idx),
};
backend.workers[idx] = Some(WorkerState {
abort_handle: handle,
queue_task,
queue,
});
Ok(())
})
}
#[derive(FromArgs)]
#[argh(subcommand, name = "net")]
/// Net device
pub struct Options {
#[argh(option, arg_name = "SOCKET_PATH,IP_ADDR,NET_MASK,MAC_ADDR")]
/// TAP device config. (e.g. "path/to/sock,10.0.2.2,255.255.255.0,12:34:56:78:9a:bc")
device: Vec<String>,
#[argh(option, arg_name = "SOCKET_PATH,TAP_FD")]
/// TAP FD with a socket path"
tap_fd: Vec<String>,
#[argh(option, arg_name = "DEVICE,IP_ADDR,NET_MASK,MAC_ADDR")]
/// 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")
vvu_device: Vec<String>,
#[argh(option, arg_name = "DEVICE,TAP_FD")]
/// TAP FD with a vfio device name for virtio-vhost-user
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 fn start_device(opts: Options) -> anyhow::Result<()> {
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 {
let ex = Executor::new().context("failed to create executor")?;
match conn {
Connection::Socket(socket) => {
threads.push(thread::spawn(move || {
NET_EXECUTOR.with(|thread_ex| {
let _ = thread_ex.set(ex.clone());
});
let listener = VhostUserListener::new_socket(&socket, None)?;
// run_until() returns an Result<Result<..>> which the ? operator lets us
// flatten.
ex.run_until(listener.run_backend(Box::new(backend), &ex))?
}));
}
Connection::Vfio(device_name) => {
threads.push(thread::spawn(move || {
NET_EXECUTOR.with(|thread_ex| {
let _ = thread_ex.set(ex.clone());
});
let listener = VhostUserListener::new_vvu(
PciAddress::from_str(&device_name)?,
backend.max_queue_num(),
None,
)?;
// run_until() returns an Result<Result<..>> which the ? operator lets us
// flatten.
ex.run_until(listener.run_backend(Box::new(backend), &ex))?
}));
}
};
}
info!("vhost-user net device ready, loop threads started.");
for t in threads {
match t.join() {
Ok(r) => r?,
Err(e) => bail!("thread panicked: {:?}", e),
}
}
Ok(())
}