mirror of
https://chromium.googlesource.com/crosvm/crosvm
synced 2024-11-24 12:34:31 +00:00
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:
parent
cd0e7edcc7
commit
44306221c1
8 changed files with 756 additions and 383 deletions
|
@ -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"]
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
373
devices/src/virtio/vhost/user/device/unix/net.rs
Normal file
373
devices/src/virtio/vhost/user/device/unix/net.rs
Normal 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(())
|
||||
}
|
288
devices/src/virtio/vhost/user/device/windows/net.rs
Normal file
288
devices/src/virtio/vhost/user/device/windows/net.rs
Normal 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(())
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue