Merge pull request #7675 from justxuewei/vhost-net

This commit is contained in:
Xuewei Niu
2023-11-12 20:38:18 +08:00
committed by GitHub
30 changed files with 2241 additions and 143 deletions

View File

@@ -396,6 +396,7 @@ dependencies = [
"serde_json",
"thiserror",
"threadpool",
"vhost",
"virtio-bindings",
"virtio-queue",
"vm-memory",
@@ -2071,6 +2072,18 @@ version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "vhost"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6769e8dbf5276b4376439fbf36bb880d203bf614bf7ef444198edc24b5a9f35"
dependencies = [
"bitflags 1.3.2",
"libc",
"vm-memory",
"vmm-sys-util",
]
[[package]]
name = "virtio-bindings"
version = "0.1.0"

View File

@@ -63,3 +63,4 @@ virtio-net = ["dbs-virtio-devices/virtio-net", "virtio-queue"]
virtio-fs = ["dbs-virtio-devices/virtio-fs-pro", "virtio-queue", "atomic-guest-memory"]
virtio-mem = ["dbs-virtio-devices/virtio-mem", "virtio-queue", "atomic-guest-memory"]
virtio-balloon = ["dbs-virtio-devices/virtio-balloon", "virtio-queue"]
vhost-net = ["dbs-virtio-devices/vhost-net"]

View File

@@ -17,3 +17,9 @@ pub use self::instance_info::{InstanceInfo, InstanceState};
/// Wrapper for configuring the memory and CPU of the microVM.
mod machine_config;
pub use self::machine_config::{VmConfigError, MAX_SUPPORTED_VCPUS};
/// Wrapper for configuring the virtio networking
#[cfg(any(feature = "virtio-net", feature = "vhost-net"))]
mod virtio_net;
#[cfg(any(feature = "virtio-net", feature = "vhost-net"))]
pub use virtio_net::{Backend, NetworkInterfaceConfig, NetworkInterfaceUpdateConfig, VirtioConfig};

View File

@@ -0,0 +1,217 @@
// Copyright (C) 2019-2023 Alibaba Cloud. All rights reserved.
// Copyright (C) 2019-2023 Ant Group. All rights reserved.
//
// SPDX-License-Identifier: Apache-2.0
use dbs_utils::net::MacAddr;
use serde::{Deserialize, Serialize};
#[cfg(feature = "virtio-net")]
use super::{VirtioNetDeviceConfigInfo, VirtioNetDeviceConfigUpdateInfo};
use crate::config_manager::RateLimiterConfigInfo;
#[cfg(feature = "vhost-net")]
use crate::device_manager::vhost_net_dev_mgr::{self, VhostNetDeviceConfigInfo};
#[cfg(feature = "virtio-net")]
use crate::device_manager::virtio_net_dev_mgr;
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
#[serde(tag = "type", deny_unknown_fields)]
/// An enum to specify a backend of Virtio network
pub enum Backend {
#[serde(rename = "virtio")]
#[cfg(feature = "virtio-net")]
/// Virtio-net
Virtio(VirtioConfig),
#[serde(rename = "vhost")]
#[cfg(feature = "vhost-net")]
/// Vhost-net
Vhost(VirtioConfig),
}
impl Default for Backend {
fn default() -> Self {
Self::Virtio(VirtioConfig::default())
}
}
/// Virtio network config, working for virtio-net and vhost-net.
#[derive(Clone, Debug, Default, Deserialize, PartialEq, Eq, Serialize)]
pub struct VirtioConfig {
/// ID of the guest network interface.
pub iface_id: String,
/// Host level path for the guest network interface.
pub host_dev_name: String,
/// Rate Limiter for received packages.
pub rx_rate_limiter: Option<RateLimiterConfigInfo>,
/// Rate Limiter for transmitted packages.
pub tx_rate_limiter: Option<RateLimiterConfigInfo>,
/// Allow duplicate mac
pub allow_duplicate_mac: bool,
}
/// This struct represents the strongly typed equivalent of the json body from
/// net iface related requests.
/// This struct works with virtio-net devices and vhost-net devices.
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, Default)]
#[serde(deny_unknown_fields)]
pub struct NetworkInterfaceConfig {
/// Number of virtqueue pairs to use. (https://www.linux-kvm.org/page/Multiqueue)
pub num_queues: Option<usize>,
/// Size of each virtqueue.
pub queue_size: Option<u16>,
/// Net backend driver.
#[serde(default = "Backend::default")]
pub backend: Backend,
/// mac of the interface.
pub guest_mac: Option<MacAddr>,
/// Use shared irq
pub use_shared_irq: Option<bool>,
/// Use generic irq
pub use_generic_irq: Option<bool>,
}
#[cfg(feature = "virtio-net")]
impl From<NetworkInterfaceConfig> for VirtioNetDeviceConfigInfo {
fn from(value: NetworkInterfaceConfig) -> Self {
let self_ref = &value;
self_ref.into()
}
}
#[cfg(feature = "virtio-net")]
impl From<&NetworkInterfaceConfig> for VirtioNetDeviceConfigInfo {
fn from(value: &NetworkInterfaceConfig) -> Self {
let queue_size = value
.queue_size
.unwrap_or(virtio_net_dev_mgr::DEFAULT_QUEUE_SIZE);
// It is safe because we tested the type of config before.
let config = match &value.backend {
Backend::Virtio(config) => config,
_ => panic!("The virtio backend config is invalid: {:?}", value),
};
Self {
iface_id: config.iface_id.clone(),
host_dev_name: config.host_dev_name.clone(),
num_queues: virtio_net_dev_mgr::DEFAULT_NUM_QUEUES,
queue_size,
guest_mac: value.guest_mac,
rx_rate_limiter: config.rx_rate_limiter.clone(),
tx_rate_limiter: config.tx_rate_limiter.clone(),
allow_duplicate_mac: config.allow_duplicate_mac,
use_shared_irq: value.use_shared_irq,
use_generic_irq: value.use_generic_irq,
}
}
}
#[cfg(feature = "vhost-net")]
impl From<NetworkInterfaceConfig> for VhostNetDeviceConfigInfo {
fn from(value: NetworkInterfaceConfig) -> Self {
let self_ref = &value;
self_ref.into()
}
}
#[cfg(feature = "vhost-net")]
impl From<&NetworkInterfaceConfig> for VhostNetDeviceConfigInfo {
fn from(value: &NetworkInterfaceConfig) -> Self {
let num_queues = value
.num_queues
.unwrap_or(vhost_net_dev_mgr::DEFAULT_NUM_QUEUES);
let queue_size = value
.queue_size
.unwrap_or(vhost_net_dev_mgr::DEFAULT_QUEUE_SIZE);
// It is safe because we tested the type of config before.
let config = match &value.backend {
Backend::Vhost(config) => config,
_ => panic!("The virtio backend config is invalid: {:?}", value),
};
Self {
iface_id: config.iface_id.clone(),
host_dev_name: config.host_dev_name.clone(),
num_queues,
vq_pairs: num_queues / 2,
queue_size,
guest_mac: value.guest_mac,
allow_duplicate_mac: config.allow_duplicate_mac,
use_shared_irq: value.use_shared_irq,
use_generic_irq: value.use_generic_irq,
}
}
}
/// The data fed into a network iface update request. Currently, only the RX and
/// TX rate limiters can be updated.
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, Default)]
#[serde(deny_unknown_fields)]
pub struct NetworkInterfaceUpdateConfig {
/// ID of the guest network interface.
pub iface_id: String,
/// New RX rate limiter config. Only provided data will be updated. I.e. if any optional data
/// is missing, it will not be nullified, but left unchanged.
pub rx_rate_limiter: Option<RateLimiterConfigInfo>,
/// New TX rate limiter config. Only provided data will be updated. I.e. if any optional data
/// is missing, it will not be nullified, but left unchanged.
pub tx_rate_limiter: Option<RateLimiterConfigInfo>,
}
#[cfg(feature = "virtio-net")]
impl From<NetworkInterfaceUpdateConfig> for VirtioNetDeviceConfigUpdateInfo {
fn from(value: NetworkInterfaceUpdateConfig) -> Self {
let self_ref = &value;
self_ref.into()
}
}
#[cfg(feature = "virtio-net")]
impl From<&NetworkInterfaceUpdateConfig> for VirtioNetDeviceConfigUpdateInfo {
fn from(value: &NetworkInterfaceUpdateConfig) -> Self {
Self {
iface_id: value.iface_id.clone(),
rx_rate_limiter: value.rx_rate_limiter.clone(),
tx_rate_limiter: value.tx_rate_limiter.clone(),
}
}
}
#[cfg(test)]
mod tests {
use dbs_utils::net::MacAddr;
use crate::api::v1::Backend;
use super::NetworkInterfaceConfig;
#[test]
fn test_network_interface_config() {
let json_str = r#"{
"num_queues": 4,
"queue_size": 512,
"backend": {
"type": "virtio",
"iface_id": "eth0",
"host_dev_name": "tap0",
"allow_duplicate_mac": true
},
"guest_mac": "81:87:1D:00:08:A9"
}"#;
let net_config: NetworkInterfaceConfig = serde_json::from_str(json_str).unwrap();
assert_eq!(net_config.num_queues, Some(4));
assert_eq!(net_config.queue_size, Some(512));
assert_eq!(
net_config.guest_mac,
Some(MacAddr::from_bytes(&[129, 135, 29, 0, 8, 169]).unwrap())
);
if let Backend::Virtio(config) = net_config.backend {
assert_eq!(config.iface_id, "eth0");
assert_eq!(config.host_dev_name, "tap0");
assert!(config.allow_duplicate_mac);
} else {
panic!("Unexpected backend type");
}
}
}

View File

@@ -36,6 +36,10 @@ pub use crate::device_manager::fs_dev_mgr::{
};
#[cfg(feature = "virtio-mem")]
pub use crate::device_manager::mem_dev_mgr::{MemDeviceConfigInfo, MemDeviceError};
#[cfg(feature = "vhost-net")]
pub use crate::device_manager::vhost_net_dev_mgr::{
VhostNetDeviceConfigInfo, VhostNetDeviceError, VhostNetDeviceMgr,
};
#[cfg(feature = "virtio-net")]
pub use crate::device_manager::virtio_net_dev_mgr::{
VirtioNetDeviceConfigInfo, VirtioNetDeviceConfigUpdateInfo, VirtioNetDeviceError,
@@ -101,6 +105,11 @@ pub enum VmmActionError {
#[error("virtio-net device error: {0}")]
VirtioNet(#[source] VirtioNetDeviceError),
#[cfg(feature = "vhost-net")]
#[error("vhost-net device error: {0:?}")]
/// Vhost-net device relared errors.
VhostNet(#[source] VhostNetDeviceError),
#[cfg(feature = "virtio-fs")]
/// The action `InsertFsDevice` failed either because of bad user input or an internal error.
#[error("virtio-fs device error: {0}")]
@@ -182,11 +191,11 @@ pub enum VmmAction {
/// are the RX and TX rate limiters.
UpdateBlockDevice(BlockDeviceConfigUpdateInfo),
#[cfg(feature = "virtio-net")]
#[cfg(any(feature = "virtio-net", feature = "vhost-net"))]
/// Add a new network interface config or update one that already exists using the
/// `NetworkInterfaceConfig` as input. This action can only be called before the microVM has
/// booted. The response is sent using the `OutcomeSender`.
InsertNetworkDevice(VirtioNetDeviceConfigInfo),
InsertNetworkDevice(NetworkInterfaceConfig),
#[cfg(feature = "virtio-net")]
/// Update a network interface, after microVM start. Currently, the only updatable properties
@@ -309,10 +318,12 @@ impl VmmService {
VmmAction::RemoveBlockDevice(drive_id) => {
self.remove_block_device(vmm, event_mgr, &drive_id)
}
VmmAction::InsertNetworkDevice(config) => match config.backend {
#[cfg(feature = "virtio-net")]
VmmAction::InsertNetworkDevice(virtio_net_cfg) => {
self.add_virtio_net_device(vmm, event_mgr, virtio_net_cfg)
}
Backend::Virtio(_) => self.add_virtio_net_device(vmm, event_mgr, config.into()),
#[cfg(feature = "vhost-net")]
Backend::Vhost(_) => self.add_vhost_net_device(vmm, event_mgr, config.into()),
},
#[cfg(feature = "virtio-net")]
VmmAction::UpdateNetworkInterface(netif_update) => {
self.update_net_rate_limiters(vmm, netif_update)
@@ -676,6 +687,28 @@ impl VmmService {
.map_err(VmmActionError::VirtioNet)
}
#[cfg(feature = "vhost-net")]
fn add_vhost_net_device(
&mut self,
vmm: &mut Vmm,
event_mgr: &mut EventManager,
config: VhostNetDeviceConfigInfo,
) -> VmmRequestResult {
let vm = vmm.get_vm_mut().ok_or(VmmActionError::InvalidVMID)?;
let ctx = vm
.create_device_op_context(Some(event_mgr.epoll_manager()))
.map_err(|err| match err {
StartMicroVmError::MicroVMAlreadyRunning => {
VmmActionError::VhostNet(VhostNetDeviceError::UpdateNotAllowedPostBoot)
}
StartMicroVmError::UpcallServerNotReady => VmmActionError::UpcallServerNotReady,
_ => VmmActionError::StartMicroVm(err),
})?;
VhostNetDeviceMgr::insert_device(vm.device_manager_mut(), ctx, config)
.map(|_| VmmData::Empty)
.map_err(VmmActionError::VhostNet)
}
#[cfg(feature = "virtio-fs")]
#[instrument(skip(self))]
fn add_fs_device(&mut self, vmm: &mut Vmm, config: FsDeviceConfigInfo) -> VmmRequestResult {
@@ -1497,7 +1530,7 @@ mod tests {
let tests = &mut [
// hotplug unready
TestData::new(
VmmAction::InsertNetworkDevice(VirtioNetDeviceConfigInfo::default()),
VmmAction::InsertNetworkDevice(NetworkInterfaceConfig::default()),
InstanceState::Running,
&|result| {
assert!(matches!(
@@ -1516,7 +1549,7 @@ mod tests {
),
// success
TestData::new(
VmmAction::InsertNetworkDevice(VirtioNetDeviceConfigInfo::default()),
VmmAction::InsertNetworkDevice(NetworkInterfaceConfig::default()),
InstanceState::Uninitialized,
&|result| {
assert!(result.is_ok());

View File

@@ -37,6 +37,7 @@ virtio-queue = "0.7.0"
vmm-sys-util = "0.11.0"
vm-memory = { version = "0.10.0", features = [ "backend-mmap" ] }
sendfd = "0.4.3"
vhost-rs = { version = "0.6.1", package = "vhost", optional = true }
[dev-dependencies]
vm-memory = { version = "0.10.0", features = [ "backend-mmap", "backend-atomic" ] }
@@ -50,3 +51,5 @@ virtio-fs = ["virtio-mmio", "fuse-backend-rs/virtiofs", "nydus-rafs/virtio-fs"]
virtio-fs-pro = ["virtio-fs", "nydus-storage/backend-registry", "nydus-storage/backend-oss"]
virtio-mem = ["virtio-mmio"]
virtio-balloon = ["virtio-mmio"]
vhost = ["virtio-mmio", "vhost-rs/vhost-user-master", "vhost-rs/vhost-kern"]
vhost-net = ["virtio-net", "vhost", "vhost-rs/vhost-net"]

View File

@@ -41,8 +41,12 @@ pub mod mem;
#[cfg(feature = "virtio-balloon")]
pub mod balloon;
#[cfg(feature = "vhost")]
pub mod vhost;
use std::io::Error as IOError;
use net::NetError;
use virtio_queue::Error as VqError;
use vm_memory::{GuestAddress, GuestAddressSpace, GuestMemoryError};
@@ -201,15 +205,20 @@ pub enum Error {
#[error("virtio-vsock error: {0}")]
VirtioVsockError(#[from] self::vsock::VsockError),
#[cfg(feature = "virtio-net")]
#[error("Virtio-net error: {0}")]
VirtioNetError(#[from] crate::net::NetError),
#[cfg(feature = "virtio-fs")]
/// Error from Virtio fs.
#[error("virtio-fs error: {0}")]
VirtioFs(fs::Error),
#[cfg(feature = "virtio-net")]
#[error("virtio-net error: {0:?}")]
VirtioNet(NetError),
#[cfg(feature = "vhost-net")]
#[error("vhost-net error: {0:?}")]
/// Error from vhost-net.
VhostNet(vhost::vhost_kern::net::Error),
#[cfg(feature = "virtio-mem")]
#[error("Virtio-mem error: {0}")]
VirtioMemError(#[from] mem::MemError),
@@ -219,6 +228,26 @@ pub enum Error {
VirtioBalloonError(#[from] balloon::BalloonError),
}
// Error for tap devices
#[cfg(feature = "virtio-net")]
#[derive(Debug, thiserror::Error)]
pub enum TapError {
#[error("missing {0} flags")]
MissingFlags(String),
#[error("failed to set offload: {0:?}")]
SetOffload(#[source] dbs_utils::net::TapError),
#[error("failed to set vnet_hdr_size: {0}")]
SetVnetHdrSize(#[source] dbs_utils::net::TapError),
#[error("failed to open a tap device: {0}")]
Open(#[source] dbs_utils::net::TapError),
#[error("failed to enable a tap device: {0}")]
Enable(#[source] dbs_utils::net::TapError),
}
/// Specialized std::result::Result for Virtio device operations.
pub type Result<T> = std::result::Result<T, Error>;

View File

@@ -31,8 +31,8 @@ use vmm_sys_util::eventfd::EventFd;
use crate::device::{VirtioDeviceConfig, VirtioDeviceInfo};
use crate::{
ActivateError, ActivateResult, ConfigResult, DbsGuestAddressSpace, Error, Result, VirtioDevice,
VirtioQueueConfig, TYPE_NET,
ActivateError, ActivateResult, ConfigResult, DbsGuestAddressSpace, Error, Result, TapError,
VirtioDevice, VirtioQueueConfig, TYPE_NET,
};
const NET_DRIVER_NAME: &str = "virtio-net";
@@ -60,15 +60,8 @@ pub const NET_EVENTS_COUNT: u32 = 6;
/// Error for virtio-net devices to handle requests from guests.
#[derive(Debug, thiserror::Error)]
pub enum NetError {
/// Open tap device failed.
#[error("open tap device failed: {0}")]
TapOpen(#[source] dbs_utils::net::TapError),
/// Setting tap interface offload flags failed.
#[error("set tap device vnet header size failed: {0}")]
TapSetOffload(#[source] dbs_utils::net::TapError),
/// Setting vnet header size failed.
#[error("set tap device vnet header size failed: {0}")]
TapSetVnetHdrSize(#[source] dbs_utils::net::TapError),
#[error("tap device operation error: {0:?}")]
TapError(#[source] TapError),
}
/// Metrics specific to the net device.
@@ -150,7 +143,7 @@ impl<Q: QueueT> RxVirtio<Q> {
}
}
fn vnet_hdr_len() -> usize {
pub fn vnet_hdr_len() -> usize {
mem::size_of::<virtio_net_hdr_v1>()
}
@@ -665,11 +658,11 @@ impl<AS: GuestAddressSpace> Net<AS> {
tap.set_offload(
net_gen::TUN_F_CSUM | net_gen::TUN_F_UFO | net_gen::TUN_F_TSO4 | net_gen::TUN_F_TSO6,
)
.map_err(NetError::TapSetOffload)?;
.map_err(|err| Error::VirtioNet(NetError::TapError(TapError::SetOffload(err))))?;
let vnet_hdr_size = vnet_hdr_len() as i32;
tap.set_vnet_hdr_size(vnet_hdr_size)
.map_err(NetError::TapSetVnetHdrSize)?;
.map_err(|err| Error::VirtioNet(NetError::TapError(TapError::SetVnetHdrSize(err))))?;
info!("net tap set finished");
let mut avail_features = 1u64 << VIRTIO_NET_F_GUEST_CSUM
@@ -722,7 +715,8 @@ impl<AS: GuestAddressSpace> Net<AS> {
tx_rate_limiter: Option<RateLimiter>,
) -> Result<Self> {
info!("open net tap {}", host_dev_name);
let tap = Tap::open_named(host_dev_name.as_str(), false).map_err(NetError::TapOpen)?;
let tap = Tap::open_named(host_dev_name.as_str(), false)
.map_err(|err| Error::VirtioNet(NetError::TapError(TapError::Open(err))))?;
info!("net tap opened");
Self::new_with_tap(

View File

@@ -0,0 +1,8 @@
// Copyright (C) 2019-2023 Alibaba Cloud. All rights reserved.
// Copyright (C) 2019-2023 Ant Group. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
//! Vhost-based virtio device backend implementations.
#[cfg(feature = "vhost-net")]
pub mod vhost_kern;

View File

@@ -0,0 +1,11 @@
// Copyright (C) 2019-2023 Alibaba Cloud. All rights reserved.
// Copyright (C) 2019-2023 Ant Group. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
//! Vhost-based virtio device backend implementations.
#[cfg(feature = "vhost-net")]
pub mod net;
#[cfg(all(feature = "vhost-net", test))]
pub mod test_utils;

View File

@@ -0,0 +1,965 @@
// Copyright (C) 2019-2023 Alibaba Cloud. All rights reserved.
// Copyright (C) 2019-2023 Ant Group. All rights reserved.
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
//
// Portions Copyright 2017 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 THIRD-PARTY file.
use std::marker::PhantomData;
use std::ops::Deref;
use std::sync::Arc;
use dbs_device::resources::ResourceConstraint;
use dbs_utils::epoll_manager::{
EpollManager, EventOps, EventSet, Events, MutEventSubscriber, SubscriberId,
};
use dbs_utils::metric::IncMetric;
use dbs_utils::net::{net_gen, MacAddr, Tap};
use log::{debug, error, info, trace, warn};
#[cfg(not(test))]
use vhost_rs::net::VhostNet as VhostNetTrait;
#[cfg(not(test))]
use vhost_rs::vhost_kern::net::Net as VhostNet;
use vhost_rs::vhost_user::message::VhostUserVringAddrFlags;
#[cfg(not(test))]
use vhost_rs::VhostBackend;
use vhost_rs::{VhostUserMemoryRegionInfo, VringConfigData};
use virtio_bindings::bindings::virtio_net::*;
use virtio_bindings::bindings::virtio_ring::*;
use virtio_queue::{Descriptor, DescriptorChain, QueueT};
use vm_memory::{Address, Bytes, GuestMemory, GuestMemoryRegion, MemoryRegionAddress};
use crate::net::{vnet_hdr_len, NetDeviceMetrics};
#[cfg(test)]
use crate::vhost::vhost_kern::test_utils::{
MockVhostBackend as VhostBackend, MockVhostNet as VhostNet,
};
use crate::{
ActivateError, ConfigResult, DbsGuestAddressSpace, Error as VirtioError,
Result as VirtioResult, TapError, VirtioDevice, VirtioDeviceConfig, VirtioDeviceInfo, TYPE_NET,
};
const NET_DRIVER_NAME: &str = "vhost-net";
// Epoll token for control queue
const CTRL_SLOT: u32 = 0;
// Control queue size
const CTRL_QUEUE_SIZE: u16 = 64;
/// Error for vhost-net devices to handle requests from guests.
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("tap device operation error: {0:?}")]
TapError(#[source] TapError),
#[error("vhost error: {0}")]
VhostError(#[source] vhost_rs::Error),
}
/// Vhost-net device implementation
pub struct Net<AS, Q, R>
where
AS: DbsGuestAddressSpace,
Q: QueueT + Send + 'static,
R: GuestMemoryRegion + Sync + Send + 'static,
{
taps: Vec<Tap>,
vq_pairs: usize,
handles: Vec<VhostNet<AS>>,
device_info: VirtioDeviceInfo,
queue_sizes: Arc<Vec<u16>>,
ctrl_queue_size: u16,
subscriber_id: Option<SubscriberId>,
id: String,
kernel_vring_bases: Option<Vec<(u32, u32)>>,
metrics: Arc<NetDeviceMetrics>,
// Type-Q placeholder
_mark_q: PhantomData<Q>,
// Type-R placeholder
_mark_r: PhantomData<R>,
}
/// Ensure that the tap interface has the correct flags and sets the
/// offload and VNET header size to the appropriate values.
fn validate_and_configure_tap(tap: &Tap, vq_pairs: usize) -> VirtioResult<()> {
// Check if there are missing flags。
let flags = tap.if_flags();
let mut required_flags = vec![
(net_gen::IFF_TAP, "IFF_TAP"),
(net_gen::IFF_NO_PI, "IFF_NO_PI"),
(net_gen::IFF_VNET_HDR, "IFF_VNET_HDR"),
];
if vq_pairs > 1 {
required_flags.push((net_gen::IFF_MULTI_QUEUE, "IFF_MULTI_QUEUE"));
}
let missing_flags = required_flags
.iter()
.filter_map(
|(value, name)| {
if value & flags == 0 {
Some(name)
} else {
None
}
},
)
.collect::<Vec<_>>();
if !missing_flags.is_empty() {
return Err(VirtioError::VhostNet(Error::TapError(
TapError::MissingFlags(
missing_flags
.into_iter()
.map(|flag| *flag)
.collect::<Vec<&str>>()
.join(", "),
),
)));
}
let vnet_hdr_size = vnet_hdr_len() as i32;
tap.set_vnet_hdr_size(vnet_hdr_size)
.map_err(|err| VirtioError::VhostNet(Error::TapError(TapError::SetVnetHdrSize(err))))?;
Ok(())
}
impl<AS, Q, R> Net<AS, Q, R>
where
AS: DbsGuestAddressSpace,
Q: QueueT + Send + 'static,
R: GuestMemoryRegion + Sync + Send + 'static,
{
/// Create a new vhost-net device with a given tap interface.
pub fn new_with_tap(
tap: Tap,
vq_pairs: usize,
guest_mac: Option<&MacAddr>,
queue_sizes: Arc<Vec<u16>>,
event_mgr: EpollManager,
) -> VirtioResult<Self> {
trace!(target: "vhost-net", "{}: Net::new_with_tap()", NET_DRIVER_NAME);
let taps = tap
.into_mq_taps(vq_pairs)
.map_err(|err| VirtioError::VhostNet(Error::TapError(TapError::Open(err))))?;
for tap in taps.iter() {
validate_and_configure_tap(tap, vq_pairs)?;
}
let mut avail_features = 1u64 << VIRTIO_NET_F_GUEST_CSUM
| 1u64 << VIRTIO_NET_F_CSUM
| 1u64 << VIRTIO_NET_F_GUEST_TSO4
| 1u64 << VIRTIO_NET_F_GUEST_UFO
| 1u64 << VIRTIO_NET_F_HOST_TSO4
| 1u64 << VIRTIO_NET_F_HOST_UFO
| 1u64 << VIRTIO_NET_F_MRG_RXBUF
| 1u64 << VIRTIO_RING_F_INDIRECT_DESC
| 1u64 << VIRTIO_RING_F_EVENT_IDX
| 1u64 << VIRTIO_F_NOTIFY_ON_EMPTY
| 1u64 << VIRTIO_F_VERSION_1;
if vq_pairs > 1 {
avail_features |= (1 << VIRTIO_NET_F_MQ | 1 << VIRTIO_NET_F_CTRL_VQ) as u64;
}
// Network device configuration layout:
// https://docs.oasis-open.org/virtio/virtio/v1.1/csprd01/virtio-v1.1-csprd01.html#x1-2000004
// - [u8; 6]: mac address
// - u16: status
// - u16: max_virtqueue_pairs
// - u16: mtu
// - u32: speed
// - u8: duplex
let mut config_space = vec![0u8; 17];
if let Some(mac) = guest_mac {
// When this feature isn't available, the driver generates a random
// MAC address. Otherwise, it should attempt to read the device MAC
// address from the config space.
avail_features |= 1u64 << VIRTIO_NET_F_MAC;
config_space[0..6].copy_from_slice(mac.get_bytes());
} else {
avail_features &= !(1 << VIRTIO_NET_F_MAC) as u64;
}
// status: mark link as up
config_space[6] = VIRTIO_NET_S_LINK_UP as u8;
config_space[7] = 0;
// max_virtqueue_pairs: only support one rx/tx pair
config_space[8] = vq_pairs as u8;
config_space[9] = 0;
// mtu: 1500 = 1536 - vxlan header?
config_space[10] = 220;
config_space[11] = 5;
// speed: 1000Mb
config_space[12] = 232;
config_space[13] = 3;
config_space[14] = 0;
config_space[15] = 0;
// duplex: full duplex: 0x01
config_space[16] = 1;
let device_info = VirtioDeviceInfo::new(
NET_DRIVER_NAME.to_owned(),
avail_features,
queue_sizes.clone(),
config_space,
event_mgr,
);
let id = device_info.driver_name.clone();
Ok(Net {
taps,
vq_pairs,
handles: Vec::new(),
device_info,
queue_sizes,
subscriber_id: None,
ctrl_queue_size: {
if vq_pairs > 1 {
CTRL_QUEUE_SIZE
} else {
0
}
},
id,
kernel_vring_bases: None,
_mark_r: PhantomData,
_mark_q: PhantomData,
metrics: Arc::new(NetDeviceMetrics::default()),
})
}
/// Create a vhost network with the Tap name
pub fn new(
host_dev_name: String,
vq_pairs: usize,
guest_mac: Option<&MacAddr>,
queue_sizes: Arc<Vec<u16>>,
event_mgr: EpollManager,
) -> VirtioResult<Self> {
// Open a TAP interface
let tap = Tap::open_named(&host_dev_name, vq_pairs > 1)
.map_err(|err| VirtioError::VhostNet(Error::TapError(TapError::Open(err))))?;
tap.enable()
.map_err(|err| VirtioError::VhostNet(Error::TapError(TapError::Enable(err))))?;
Self::new_with_tap(tap, vq_pairs, guest_mac, queue_sizes, event_mgr)
}
fn do_device_activate(
&mut self,
config: &VirtioDeviceConfig<AS, Q, R>,
vq_pairs: usize,
) -> VirtioResult<()>
where
Q: QueueT + Send + 'static,
R: GuestMemoryRegion + Sync + Send + 'static,
{
let guard = config.lock_guest_memory();
let mem = guard.deref();
if self.handles.is_empty() {
for _ in 0..vq_pairs {
self.handles.push(
VhostNet::<AS>::new(config.vm_as.clone())
.map_err(|err| VirtioError::VhostNet(Error::VhostError(err)))?,
);
}
}
if self.kernel_vring_bases.is_none() {
self.setup_vhost_backend(config, mem)?
}
Ok(())
}
fn setup_vhost_backend(
&mut self,
config: &VirtioDeviceConfig<AS, Q, R>,
mem: &AS::M,
) -> VirtioResult<()>
where
Q: QueueT + Send + 'static,
R: GuestMemoryRegion + Sync + Send + 'static,
{
trace!(target: "vhost-net", "{}: Net::setup_vhost_backend(vq_pairs: {})", NET_DRIVER_NAME, self.vq_pairs);
if self.vq_pairs < 1 {
error!(
"{}: Invalid virtio queue pairs, expected a value greater than 0, but got {}",
NET_DRIVER_NAME, self.vq_pairs
);
return Err(VirtioError::ActivateError(ActivateError::InvalidParam));
}
if self.handles.len() != self.vq_pairs || self.taps.len() != self.vq_pairs {
error!("{}: Invalid handlers or taps, handlers length {}, taps length {}, virtio queue pairs = {}",
NET_DRIVER_NAME,
self.handles.len(),
self.taps.len(),
self.vq_pairs);
return Err(VirtioError::ActivateError(ActivateError::InternalError));
}
for idx in 0..self.vq_pairs {
self.init_vhost_dev(idx, config, mem)?;
}
self.kernel_vring_bases = None;
Ok(())
}
fn init_vhost_dev(
&mut self,
pair_index: usize,
config: &VirtioDeviceConfig<AS, Q, R>,
mem: &AS::M,
) -> VirtioResult<()>
where
Q: QueueT + Send + 'static,
R: GuestMemoryRegion + Sync + Send + 'static,
{
trace!(target: "vhost-net", "{}: Net::init_vhost_dev(pair_index: {})", NET_DRIVER_NAME, pair_index);
let handle = &mut self.handles[pair_index];
handle
.set_owner()
.map_err(|err| VirtioError::VhostNet(Error::VhostError(err)))?;
let avail_features = handle
.get_features()
.map_err(|err| VirtioError::VhostNet(Error::VhostError(err)))?;
let features = self.device_info.acked_features() & avail_features;
handle
.set_features(features)
.map_err(|err| VirtioError::VhostNet(Error::VhostError(err)))?;
let tap = &self.taps[pair_index];
tap.set_offload(Self::virtio_features_to_tap_offload(
self.device_info.acked_features(),
))
.map_err(|err| VirtioError::VhostNet(Error::TapError(TapError::SetOffload(err))))?;
self.setup_mem_table(pair_index, mem)?;
self.init_vhost_queues(pair_index, config)?;
Ok(())
}
fn setup_mem_table(&mut self, pair_index: usize, mem: &AS::M) -> VirtioResult<()> {
let handle = &mut self.handles[pair_index];
let mut regions = Vec::new();
for region in mem.iter() {
let guest_phys_addr = region.start_addr();
let userspace_addr = region
.get_host_address(MemoryRegionAddress(0))
.map_err(|_| VirtioError::InvalidGuestAddress(guest_phys_addr))?;
regions.push(VhostUserMemoryRegionInfo {
guest_phys_addr: guest_phys_addr.raw_value(),
memory_size: region.len(),
userspace_addr: userspace_addr as *const u8 as u64,
mmap_offset: 0,
mmap_handle: -1,
});
}
handle
.set_mem_table(&regions)
.map_err(|err| VirtioError::VhostNet(Error::VhostError(err)))?;
Ok(())
}
fn init_vhost_queues(
&mut self,
pair_index: usize,
config: &VirtioDeviceConfig<AS, Q, R>,
) -> VirtioResult<()>
where
Q: QueueT + Send + 'static,
R: GuestMemoryRegion + Sync + Send + 'static,
{
trace!(target: "vhost-net", "{}: Net::init_vhost_queues(pair_index: {})", NET_DRIVER_NAME, pair_index);
let handle = &mut self.handles[pair_index];
let tap = &self.taps[pair_index];
let intr_evts = config.get_queue_interrupt_eventfds();
assert_eq!(config.queues.len(), intr_evts.len());
let vq_pair = vec![
&config.queues[2 * pair_index],
&config.queues[2 * pair_index + 1],
];
for queue_cfg in vq_pair.iter() {
let queue = &queue_cfg.queue;
let queue_index = queue_cfg.index() as usize;
let vq_index = queue_index % 2;
handle
.set_vring_num(vq_index, queue_cfg.queue.size())
.map_err(|err| VirtioError::VhostNet(Error::VhostError(err)))?;
if let Some(vring_base) = &self.kernel_vring_bases {
let base = if vq_index == 0 {
vring_base[pair_index].0
} else {
vring_base[pair_index].1
};
handle
.set_vring_base(vq_index, base as u16)
.map_err(|err| VirtioError::VhostNet(Error::VhostError(err)))?;
} else {
handle
.set_vring_base(vq_index, 0)
.map_err(|err| VirtioError::VhostNet(Error::VhostError(err)))?;
}
let config_data = &VringConfigData {
queue_max_size: queue.max_size(),
queue_size: queue.size(),
flags: VhostUserVringAddrFlags::empty().bits(),
desc_table_addr: queue.desc_table(),
used_ring_addr: queue.used_ring(),
avail_ring_addr: queue.avail_ring(),
log_addr: None,
};
handle
.set_vring_addr(vq_index, config_data)
.map_err(|err| VirtioError::VhostNet(Error::VhostError(err)))?;
handle
.set_vring_call(vq_index, intr_evts[queue_index])
.map_err(|err| VirtioError::VhostNet(Error::VhostError(err)))?;
handle
.set_vring_kick(vq_index, &queue_cfg.eventfd)
.map_err(|err| VirtioError::VhostNet(Error::VhostError(err)))?;
handle
.set_backend(vq_index, Some(&tap.tap_file))
.map_err(|err| VirtioError::VhostNet(Error::VhostError(err)))?;
}
Ok(())
}
fn virtio_features_to_tap_offload(features: u64) -> u32 {
let mut tap_offloads: u32 = 0;
if features & (1 << VIRTIO_NET_F_GUEST_CSUM) != 0 {
tap_offloads |= net_gen::TUN_F_CSUM;
}
if features & (1 << VIRTIO_NET_F_GUEST_TSO4) != 0 {
tap_offloads |= net_gen::TUN_F_TSO4;
}
if features & (1 << VIRTIO_NET_F_GUEST_TSO6) != 0 {
tap_offloads |= net_gen::TUN_F_TSO6;
}
if features & (1 << VIRTIO_NET_F_GUEST_ECN) != 0 {
tap_offloads |= net_gen::TUN_F_TSO_ECN;
}
if features & (1 << VIRTIO_NET_F_GUEST_UFO) != 0 {
tap_offloads |= net_gen::TUN_F_UFO;
}
tap_offloads
}
}
impl<AS, Q, R> VirtioDevice<AS, Q, R> for Net<AS, Q, R>
where
AS: DbsGuestAddressSpace,
Q: QueueT + Send + 'static,
R: GuestMemoryRegion + Sync + Send + 'static,
{
fn device_type(&self) -> u32 {
TYPE_NET
}
fn queue_max_sizes(&self) -> &[u16] {
&self.queue_sizes
}
fn ctrl_queue_max_sizes(&self) -> u16 {
self.ctrl_queue_size
}
fn get_avail_features(&self, page: u32) -> u32 {
self.device_info.get_avail_features(page)
}
fn set_acked_features(&mut self, page: u32, value: u32) {
trace!(target: "vhost-net", "{}: Net::set_acked_features({}, 0x{:x})",
self.id, page, value);
self.device_info.set_acked_features(page, value);
}
fn read_config(&mut self, offset: u64, data: &mut [u8]) -> ConfigResult {
trace!(target: "vhost-net", "{}: Net::read_config(0x{:x}, {:?})",
self.id, offset, data);
self.device_info.read_config(offset, data).map_err(|e| {
self.metrics.cfg_fails.inc();
e
})
}
fn write_config(&mut self, offset: u64, data: &[u8]) -> ConfigResult {
trace!(target: "vhost-net", "{}: Net::write_config(0x{:x}, {:?})",
self.id, offset, data);
self.device_info.write_config(offset, data).map_err(|e| {
self.metrics.cfg_fails.inc();
e
})
}
fn activate(&mut self, config: crate::VirtioDeviceConfig<AS, Q, R>) -> crate::ActivateResult {
trace!(target: "vhost-net", "{}: Net::activate()", self.id);
// Do not support control queue and multi-queue.
let vq_pairs = config.queues.len() / 2;
if config.queues.len() % 2 != 0 || self.taps.len() != vq_pairs {
self.metrics.activate_fails.inc();
return Err(crate::ActivateError::InvalidParam);
}
self.device_info
.check_queue_sizes(&config.queues)
.map_err(|err| {
self.metrics.activate_fails.inc();
err
})?;
if let Err(err) = self.do_device_activate(&config, vq_pairs) {
error!(target: "vhost-net", "device {:?} activate failed: {:?}", self.id, err);
panic!("vhost-net device {:?} activate failed: {:?}", self.id, err);
}
let handler = Box::new(NetEpollHandler {
config,
id: self.id.clone(),
});
self.subscriber_id = Some(self.device_info.register_event_handler(handler));
Ok(())
}
fn get_resource_requirements(
&self,
requests: &mut Vec<dbs_device::resources::ResourceConstraint>,
use_generic_irq: bool,
) {
trace!(target: "vhost-net", "{}: Net::get_resource_requirements()", self.id);
requests.push(ResourceConstraint::LegacyIrq { irq: None });
if use_generic_irq {
requests.push(ResourceConstraint::GenericIrq {
size: (self.queue_sizes.len() + 1) as u32,
});
}
}
fn remove(&mut self) {
self.taps.clear();
let subscriber_id = self.subscriber_id.take();
if let Some(subscriber_id) = subscriber_id {
match self.device_info.remove_event_handler(subscriber_id) {
Ok(_) => debug!("vhost-net: removed subscriber_id {:?}", self.subscriber_id),
Err(err) => warn!("vhost-net: failed to remove event handler: {:?}", err),
}
}
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
self
}
}
trait FromNetCtrl<T> {
fn from_net_ctrl_st<M: GuestMemory>(mem: &M, desc: &Descriptor) -> VirtioResult<T> {
let mut buf = vec![0u8; std::mem::size_of::<T>()];
match mem.read_slice(&mut buf, desc.addr()) {
Ok(_) => unsafe { Ok(std::ptr::read_volatile(&buf[..] as *const _ as *const T)) },
Err(err) => {
error!("Failed to read from memory, {}", err);
Err(VirtioError::InternalError)
}
}
}
}
impl FromNetCtrl<virtio_net_ctrl_hdr> for virtio_net_ctrl_hdr {}
impl FromNetCtrl<virtio_net_ctrl_mq> for virtio_net_ctrl_mq {}
pub(crate) struct NetEpollHandler<AS, Q, R>
where
AS: DbsGuestAddressSpace,
Q: QueueT + Send + 'static,
R: GuestMemoryRegion + Sync + Send + 'static,
{
pub(crate) config: VirtioDeviceConfig<AS, Q, R>,
id: String,
}
impl<AS, Q, R> NetEpollHandler<AS, Q, R>
where
AS: DbsGuestAddressSpace,
Q: QueueT + Send + 'static,
R: GuestMemoryRegion + Sync + Send + 'static,
{
fn process_ctrl_request(&mut self) -> VirtioResult<()> {
let guard = self.config.lock_guest_memory();
let mem = guard.deref();
let cvq = self.config.ctrl_queue.as_mut().unwrap();
while let Some(mut desc_chain) = cvq.get_next_descriptor(mem)? {
let len = match Self::process_ctrl_desc(&mut desc_chain, mem) {
Ok(len) => {
debug!("{}: process ctrl desc succeed!", self.id);
len
}
Err(err) => {
debug!(
"{}: failed to process control queue request, {}",
self.id, err
);
0
}
};
cvq.add_used(mem, desc_chain.head_index(), len);
}
Ok(())
}
fn process_ctrl_desc(
desc_chain: &mut DescriptorChain<&AS::M>,
mem: &AS::M,
) -> VirtioResult<u32> {
if let Some(header) = desc_chain.next() {
let ctrl_hdr = virtio_net_ctrl_hdr::from_net_ctrl_st(mem, &header)?;
match ctrl_hdr.class as u32 {
VIRTIO_NET_CTRL_MQ => {
Self::virtio_handle_ctrl_mq(desc_chain, ctrl_hdr.cmd, mem)?;
return Self::virtio_handle_ctrl_status(desc_chain, VIRTIO_NET_OK as u8, mem);
}
_ => error!(
"{}: unknown net control request class: 0x{:x}",
NET_DRIVER_NAME, ctrl_hdr.class
),
}
}
Ok(0)
}
fn virtio_handle_ctrl_mq(
desc_chain: &mut DescriptorChain<&AS::M>,
cmd: u8,
mem: &AS::M,
) -> VirtioResult<()> {
if cmd == VIRTIO_NET_CTRL_MQ_VQ_PAIRS_SET as u8 {
if let Some(next) = desc_chain.next() {
if let Ok(ctrl_mq) = virtio_net_ctrl_mq::from_net_ctrl_st(mem, &next) {
let curr_queues = ctrl_mq.virtqueue_pairs;
info!("{}: vq pairs: {}", NET_DRIVER_NAME, curr_queues);
}
}
}
Ok(())
}
fn virtio_handle_ctrl_status(
desc_chain: &mut DescriptorChain<&AS::M>,
status: u8,
mem: &AS::M,
) -> VirtioResult<u32> {
let buf = vec![status];
let mut total = 0;
for next in desc_chain {
if next.is_write_only() {
match mem.write_slice(&buf, next.addr()) {
Ok(_) => {
debug!("{}: succeed to update virtio ctrl status!", NET_DRIVER_NAME);
total += 1;
}
Err(_) => warn!("{}: failed to update ctrl status!", NET_DRIVER_NAME),
}
}
}
Ok(total)
}
}
impl<AS, Q, R> MutEventSubscriber for NetEpollHandler<AS, Q, R>
where
AS: DbsGuestAddressSpace,
Q: QueueT + Send + 'static,
R: GuestMemoryRegion + Sync + Send + 'static,
{
fn process(&mut self, events: Events, _ops: &mut EventOps) {
match events.data() {
CTRL_SLOT => {
if let Some(config) = self.config.ctrl_queue.as_ref() {
if let Err(err) = config.consume_event() {
error!("{}: failed to read eventfd, {:?}", self.id, err);
} else if let Err(err) = self.process_ctrl_request() {
error!(
"{}: failed to handle control queue request, {:?}",
self.id, err
);
}
}
}
_ => error!("{}: unknown epoll event slot {}", self.id, events.data()),
}
}
fn init(&mut self, ops: &mut EventOps) {
trace!(target: "vhost-net", "{}: NetEpollHandler::init()", self.id);
if let Some(config) = self.config.ctrl_queue.as_ref() {
let event = Events::with_data(&config.eventfd, CTRL_SLOT, EventSet::IN);
if let Err(err) = ops.add(event) {
error!(
"{}: failed to register epoll event for control queue, {:?}",
self.id, err
);
}
}
}
}
#[cfg(test)]
mod tests {
use dbs_device::resources::DeviceResources;
use dbs_interrupt::{
InterruptIndex, InterruptManager, InterruptSourceType, InterruptStatusRegister32,
NoopNotifier,
};
use dbs_utils::epoll_manager::SubscriberOps;
use kvm_ioctls::Kvm;
use virtio_queue::{Queue, QueueSync};
use vm_memory::{GuestAddress, GuestMemoryMmap, GuestRegionMmap};
use vmm_sys_util::eventfd::EventFd;
use super::*;
use crate::tests::create_vm_and_irq_manager;
use crate::{create_queue_notifier, VirtioQueueConfig};
fn create_vhost_kern_net_epoll_handler(
id: String,
) -> NetEpollHandler<Arc<GuestMemoryMmap>, QueueSync, GuestRegionMmap> {
let queues = vec![
VirtioQueueConfig::create(128, 0).unwrap(),
VirtioQueueConfig::create(128, 0).unwrap(),
];
let ctrl_queue = VirtioQueueConfig::create(128, 0).unwrap();
let mem = GuestMemoryMmap::from_ranges(&[(GuestAddress(0), 0x10000)]).unwrap();
let kvm = Arc::new(Kvm::new().unwrap());
let vm_fd = Arc::new(kvm.create_vm().unwrap());
let resources = DeviceResources::new();
let config = VirtioDeviceConfig::new(
Arc::new(mem),
vm_fd,
resources,
queues,
Some(ctrl_queue),
Arc::new(NoopNotifier::default()),
);
NetEpollHandler { config, id }
}
#[test]
fn test_vhost_kern_net_virtio_normal() {
let guest_mac_str = "11:22:33:44:55:66";
let guest_mac = MacAddr::parse_str(guest_mac_str).unwrap();
let queue_sizes = Arc::new(vec![128]);
let epoll_mgr = EpollManager::default();
let mut dev: Net<Arc<GuestMemoryMmap>, QueueSync, GuestRegionMmap> = Net::new(
String::from("test_vhosttap"),
2,
Some(&guest_mac),
queue_sizes,
epoll_mgr,
)
.unwrap();
assert_eq!(dev.device_type(), TYPE_NET);
let queue_size = vec![128];
assert_eq!(dev.queue_max_sizes(), &queue_size[..]);
assert_eq!(
dev.get_avail_features(0),
dev.device_info.get_avail_features(0)
);
assert_eq!(
dev.get_avail_features(1),
dev.device_info.get_avail_features(1)
);
assert_eq!(
dev.get_avail_features(2),
dev.device_info.get_avail_features(2)
);
dev.set_acked_features(2, 0);
assert_eq!(dev.get_avail_features(2), 0);
let config: [u8; 8] = [0; 8];
dev.write_config(0, &config).unwrap();
let mut data: [u8; 8] = [1; 8];
dev.read_config(0, &mut data).unwrap();
assert_eq!(config, data);
}
#[test]
fn test_vhost_kern_net_virtio_activate() {
let guest_mac_str = "11:22:33:44:55:66";
let guest_mac = MacAddr::parse_str(guest_mac_str).unwrap();
// Invalid vq_pairs
{
let queue_sizes = Arc::new(vec![128, 128]);
let epoll_mgr = EpollManager::default();
let mut dev: Net<Arc<GuestMemoryMmap>, QueueSync, GuestRegionMmap> = Net::new(
String::from("test_vhosttap"),
2,
Some(&guest_mac),
queue_sizes,
epoll_mgr,
)
.unwrap();
// The length of queues should be 4.
let queues = vec![
VirtioQueueConfig::create(128, 0).unwrap(),
VirtioQueueConfig::create(128, 0).unwrap(),
];
let mem = GuestMemoryMmap::from_ranges(&[(GuestAddress(0), 0x10000)]).unwrap();
let kvm = Kvm::new().unwrap();
let vm_fd = Arc::new(kvm.create_vm().unwrap());
let resources = DeviceResources::new();
let config = VirtioDeviceConfig::new(
Arc::new(mem),
vm_fd,
resources,
queues,
None,
Arc::new(NoopNotifier::default()),
);
assert!(dev.activate(config).is_err())
}
// Invalid queue sizes
{
let queue_sizes = Arc::new(vec![128]);
let epoll_mgr = EpollManager::default();
let mut dev: Net<Arc<GuestMemoryMmap>, QueueSync, GuestRegionMmap> = Net::new(
String::from("test_vhosttap"),
1,
Some(&guest_mac),
queue_sizes,
epoll_mgr,
)
.unwrap();
let queues = vec![
VirtioQueueConfig::create(128, 0).unwrap(),
VirtioQueueConfig::create(128, 0).unwrap(),
];
let mem = GuestMemoryMmap::from_ranges(&[(GuestAddress(0), 0x10000)]).unwrap();
let kvm = Kvm::new().unwrap();
let vm_fd = Arc::new(kvm.create_vm().unwrap());
let resources = DeviceResources::new();
let config = VirtioDeviceConfig::new(
Arc::new(mem),
vm_fd,
resources,
queues,
None,
Arc::new(NoopNotifier::default()),
);
assert!(dev.activate(config).is_err());
}
// Success
{
let (_vmfd, irq_manager) = create_vm_and_irq_manager();
let group = irq_manager
.create_group(InterruptSourceType::LegacyIrq, 0, 1)
.unwrap();
let status = Arc::new(InterruptStatusRegister32::new());
let notifier = create_queue_notifier(group, status, 0u32 as InterruptIndex);
let queue: Queue = Queue::new(1024).unwrap();
let queue2 = Queue::new(1024).unwrap();
let queue_eventfd = Arc::new(EventFd::new(0).unwrap());
let queue_eventfd2 = Arc::new(EventFd::new(0).unwrap());
let queue_sizes = Arc::new(vec![128, 128]);
let epoll_mgr = EpollManager::default();
let mut dev: Net<Arc<GuestMemoryMmap>, Queue, GuestRegionMmap> = Net::new(
String::from("test_vhosttap"),
1,
Some(&guest_mac),
queue_sizes,
epoll_mgr,
)
.unwrap();
let queues = vec![
VirtioQueueConfig::new(queue, queue_eventfd, notifier.clone(), 1),
VirtioQueueConfig::new(queue2, queue_eventfd2, notifier, 1),
];
let mem = GuestMemoryMmap::from_ranges(&[(GuestAddress(0), 0x10000)]).unwrap();
let kvm = Kvm::new().unwrap();
let vm_fd = Arc::new(kvm.create_vm().unwrap());
let resources = DeviceResources::new();
let config = VirtioDeviceConfig::new(
Arc::new(mem),
vm_fd,
resources,
queues,
None,
Arc::new(NoopNotifier::default()),
);
assert!(dev.activate(config).is_ok());
}
}
#[test]
fn test_vhost_kern_net_epoll_handler_handle_event() {
let handler = create_vhost_kern_net_epoll_handler("test_1".to_string());
let event_fd = EventFd::new(0).unwrap();
let mgr = EpollManager::default();
let id = mgr.add_subscriber(Box::new(handler));
let mut inner_mgr = mgr.mgr.lock().unwrap();
let mut event_op = inner_mgr.event_ops(id).unwrap();
let event_set = EventSet::EDGE_TRIGGERED;
let mut handler = create_vhost_kern_net_epoll_handler("test_2".to_string());
// test for CTRL_SLOT
let events = Events::with_data(&event_fd, CTRL_SLOT, event_set);
handler.process(events, &mut event_op);
handler.config.queues[0].generate_event().unwrap();
handler.process(events, &mut event_op);
// test for unknown event
let events = Events::with_data(&event_fd, CTRL_SLOT + 1, event_set);
handler.process(events, &mut event_op);
}
}

View File

@@ -0,0 +1,121 @@
// Copyright (C) 2021-2023 Alibaba Cloud Computing. All rights reserved.
// Copyright (C) 2021-2023 Ant Group. All rights reserved.
// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause
use std::fs::File;
use std::os::fd::{AsRawFd, RawFd};
use vhost_rs::vhost_kern::VhostKernBackend;
use vhost_rs::{Error as VhostError, VhostUserMemoryRegionInfo, VringConfigData};
use vm_memory::GuestAddressSpace;
use vmm_sys_util::eventfd::EventFd;
pub type Result<T> = std::result::Result<T, VhostError>;
pub struct MockVhostNet<AS: GuestAddressSpace> {
pub(crate) fd: i32,
pub(crate) mem: AS,
}
impl<AS: GuestAddressSpace> MockVhostNet<AS> {
pub fn new(mem: AS) -> Result<Self> {
Ok(MockVhostNet { fd: 0, mem })
}
pub fn set_backend(&self, _queue_index: usize, _fd: Option<&File>) -> Result<()> {
Ok(())
}
}
impl<AS: GuestAddressSpace> VhostKernBackend for MockVhostNet<AS> {
type AS = AS;
fn mem(&self) -> &Self::AS {
&self.mem
}
}
impl<AS: GuestAddressSpace> AsRawFd for MockVhostNet<AS> {
fn as_raw_fd(&self) -> RawFd {
self.fd
}
}
pub trait MockVhostBackend: std::marker::Sized {
fn get_features(&mut self) -> Result<u64>;
fn set_features(&mut self, features: u64) -> Result<()>;
fn set_owner(&mut self) -> Result<()>;
fn reset_owner(&mut self) -> Result<()>;
fn set_mem_table(&mut self, regions: &[VhostUserMemoryRegionInfo]) -> Result<()>;
fn set_log_base(&mut self, base: u64, fd: Option<RawFd>) -> Result<()>;
fn set_log_fd(&mut self, fd: RawFd) -> Result<()>;
fn set_vring_num(&mut self, queue_index: usize, num: u16) -> Result<()>;
fn set_vring_addr(&mut self, queue_index: usize, config_data: &VringConfigData) -> Result<()>;
fn set_vring_base(&mut self, queue_index: usize, base: u16) -> Result<()>;
fn get_vring_base(&mut self, queue_index: usize) -> Result<u32>;
fn set_vring_call(&mut self, queue_index: usize, fd: &EventFd) -> Result<()>;
fn set_vring_kick(&mut self, queue_index: usize, fd: &EventFd) -> Result<()>;
fn set_vring_err(&mut self, queue_index: usize, fd: &EventFd) -> Result<()>;
}
impl<T: VhostKernBackend> MockVhostBackend for T {
fn set_owner(&mut self) -> Result<()> {
Ok(())
}
fn reset_owner(&mut self) -> Result<()> {
Ok(())
}
fn get_features(&mut self) -> Result<u64> {
Ok(0)
}
fn set_features(&mut self, _features: u64) -> Result<()> {
Ok(())
}
fn set_mem_table(&mut self, _regions: &[VhostUserMemoryRegionInfo]) -> Result<()> {
Ok(())
}
fn set_log_base(&mut self, _base: u64, _fd: Option<RawFd>) -> Result<()> {
Ok(())
}
fn set_log_fd(&mut self, _fd: RawFd) -> Result<()> {
Ok(())
}
fn set_vring_num(&mut self, _queue_index: usize, _num: u16) -> Result<()> {
Ok(())
}
fn set_vring_addr(
&mut self,
_queue_index: usize,
_config_data: &VringConfigData,
) -> Result<()> {
Ok(())
}
fn set_vring_base(&mut self, _queue_index: usize, _base: u16) -> Result<()> {
Ok(())
}
fn get_vring_base(&mut self, _queue_index: usize) -> Result<u32> {
Ok(0)
}
fn set_vring_call(&mut self, _queue_index: usize, _fd: &EventFd) -> Result<()> {
Ok(())
}
fn set_vring_kick(&mut self, _queue_index: usize, _fd: &EventFd) -> Result<()> {
Ok(())
}
fn set_vring_err(&mut self, _queue_index: usize, _fd: &EventFd) -> Result<()> {
Ok(())
}
}

View File

@@ -102,6 +102,12 @@ pub mod balloon_dev_mgr;
#[cfg(feature = "virtio-balloon")]
use self::balloon_dev_mgr::BalloonDeviceMgr;
#[cfg(feature = "vhost-net")]
/// Device manager for vhost-net devices.
pub mod vhost_net_dev_mgr;
#[cfg(feature = "vhost-net")]
use self::vhost_net_dev_mgr::VhostNetDeviceMgr;
macro_rules! info(
($l:expr, $($args:tt)+) => {
slog::info!($l, $($args)+; slog::o!("subsystem" => "device_manager"))
@@ -537,6 +543,9 @@ pub struct DeviceManager {
#[cfg(feature = "virtio-balloon")]
pub(crate) balloon_manager: BalloonDeviceMgr,
#[cfg(feature = "vhost-net")]
vhost_net_manager: VhostNetDeviceMgr,
}
impl DeviceManager {
@@ -573,6 +582,8 @@ impl DeviceManager {
mem_manager: MemDeviceMgr::default(),
#[cfg(feature = "virtio-balloon")]
balloon_manager: BalloonDeviceMgr::default(),
#[cfg(feature = "vhost-net")]
vhost_net_manager: VhostNetDeviceMgr::default(),
}
}
@@ -742,6 +753,14 @@ impl DeviceManager {
self.block_manager
.generate_kernel_boot_args(kernel_config)
.map_err(StartMicroVmError::DeviceManager)?;
#[cfg(feature = "vhost-net")]
self.vhost_net_manager
.attach_devices(&mut ctx)
.map_err(StartMicroVmError::VhostNetDeviceError)?;
// Ensure that all devices are attached before kernel boot args are
// generated.
ctx.generate_kernel_boot_args(kernel_config)
.map_err(StartMicroVmError::DeviceManager)?;
@@ -790,6 +809,9 @@ impl DeviceManager {
#[cfg(feature = "virtio-vsock")]
self.vsock_manager.remove_devices(&mut ctx)?;
#[cfg(feature = "vhost-net")]
self.vhost_net_manager.remove_devices(&mut ctx)?;
Ok(())
}
}
@@ -1144,6 +1166,8 @@ mod tests {
balloon_manager: BalloonDeviceMgr::default(),
#[cfg(target_arch = "aarch64")]
mmio_device_info: HashMap::new(),
#[cfg(feature = "vhost-net")]
vhost_net_manager: VhostNetDeviceMgr::default(),
logger,
shared_info,

View File

@@ -0,0 +1,647 @@
// Copyright (C) 2019-2023 Alibaba Cloud. All rights reserved.
// Copyright (C) 2019-2023 Ant Group. All rights reserved.
//
// SPDX-License-Identifier: Apache-2.0
use std::result::Result;
use std::sync::Arc;
use dbs_utils::net::MacAddr;
use dbs_virtio_devices::vhost::vhost_kern::net::Net;
use dbs_virtio_devices::Error as VirtioError;
use serde::{Deserialize, Serialize};
use virtio_queue::QueueSync;
use super::{DeviceManager, DeviceMgrError, DeviceOpContext};
use crate::address_space_manager::{GuestAddressSpaceImpl, GuestRegionImpl};
use crate::config_manager::{ConfigItem, DeviceConfigInfos};
/// Default number of virtio queues, one rx/tx pair.
pub const DEFAULT_NUM_QUEUES: usize = 2;
/// Default size of virtio queues.
pub const DEFAULT_QUEUE_SIZE: u16 = 256;
// The flag of whether to use the shared irq.
const USE_SHARED_IRQ: bool = true;
// The flag of whether to use the generic irq.
const USE_GENERIC_IRQ: bool = true;
#[derive(Debug, thiserror::Error)]
/// Errors associated with vhost-net device operations
pub enum VhostNetDeviceError {
/// The Context Identifier is already in use.
#[error("the device id {0} already exists")]
DeviceIdAlreadyExist(String),
/// The MAC address is already in use.
#[error("the guest Mac address {0} is already in use")]
GuestMacAddressInUse(String),
/// The host device name is already in use.
#[error("the host device name {0} is already in use")]
HostDeviceNameInUse(String),
/// The update isn't allowed after booting the mircovm.
#[error("update operation is not allowed after booting")]
UpdateNotAllowedPostBoot,
/// Invalid queue number for vhost-net device.
#[error("invalid queue number {0} for vhost-net device")]
InvalidQueueNum(usize),
/// Failure from device manager.
#[error("failure in device manager operations: {0:?}")]
DeviceManager(#[source] DeviceMgrError),
/// Failure from virtio subsystem.
#[error("virtio error: {0:?}")]
Virtio(VirtioError),
/// Split this at some point.
/// Internal errors are due to resource exhaustion.
/// Users errors are due to invalid permissions.
#[error("cannot create a vhost-net device: {0}")]
CreateNetDevice(#[source] VirtioError),
/// Cannot initialize a MMIO Network Device or add a device to the MMIO Bus.
#[error("failure while registering vhost-net device: {0}")]
RegisterNetDevice(#[source] DeviceMgrError),
}
/// Configuration information for vhost net devices.
/// TODO: https://github.com/kata-containers/kata-containers/issues/8382.
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub struct VhostNetDeviceConfigInfo {
/// Id of the guest network interface.
pub iface_id: String,
/// Host level path for the guest network interface.
pub host_dev_name: String,
/// Number of virtqueues to use.
pub num_queues: usize,
/// Number of vq pairs to use.
pub vq_pairs: usize,
/// Size of each virtqueue.
pub queue_size: u16,
/// Guest MAC address.
pub guest_mac: Option<MacAddr>,
/// allow duplicate mac
pub allow_duplicate_mac: bool,
/// Use shared irq
pub use_shared_irq: Option<bool>,
/// Use shared irq
pub use_generic_irq: Option<bool>,
}
impl VhostNetDeviceConfigInfo {
/// Returns a reference to the mac address. Its mac address is not
/// configured, it returns None.
pub fn guest_mac(&self) -> Option<&MacAddr> {
self.guest_mac.as_ref()
}
/// Returns rx and tx queue sizes, the length is num_queues, each value is
/// queue_size.
pub fn queue_sizes(&self) -> Vec<u16> {
let queue_size = if self.queue_size > 0 {
self.queue_size
} else {
DEFAULT_QUEUE_SIZE
};
let num_queues = if self.num_queues > 0 {
self.num_queues
} else {
DEFAULT_NUM_QUEUES
};
(0..num_queues).map(|_| queue_size).collect()
}
}
impl ConfigItem for VhostNetDeviceConfigInfo {
type Err = VhostNetDeviceError;
fn id(&self) -> &str {
&self.iface_id
}
fn check_conflicts(&self, other: &Self) -> Result<(), Self::Err> {
if self.iface_id == other.iface_id {
Err(VhostNetDeviceError::DeviceIdAlreadyExist(
self.iface_id.clone(),
))
} else if !other.allow_duplicate_mac
&& self.guest_mac.is_some()
&& self.guest_mac == other.guest_mac
{
Err(VhostNetDeviceError::GuestMacAddressInUse(
self.guest_mac.as_ref().unwrap().to_string(),
))
} else if self.host_dev_name == other.host_dev_name {
Err(VhostNetDeviceError::HostDeviceNameInUse(
self.host_dev_name.clone(),
))
} else {
Ok(())
}
}
}
/// Device manager to manage all vhost net devices.
pub struct VhostNetDeviceMgr {
info_list: DeviceConfigInfos<VhostNetDeviceConfigInfo>,
use_shared_irq: bool,
}
impl VhostNetDeviceMgr {
/// Create a `vhost_kern::net::Net` struct representing a vhost-net device.
fn create_device(
cfg: &VhostNetDeviceConfigInfo,
ctx: &mut DeviceOpContext,
) -> Result<Box<Net<GuestAddressSpaceImpl, QueueSync, GuestRegionImpl>>, VirtioError> {
slog::info!(
ctx.logger(),
"create a vhost-net device";
"subsystem" => "vhost_net_dev_mgr",
"id" => &cfg.iface_id,
"host_dev_name" => &cfg.host_dev_name,
);
let epoll_mgr = ctx.epoll_mgr.clone().ok_or(VirtioError::InvalidInput)?;
Ok(Box::new(Net::new(
cfg.host_dev_name.clone(),
cfg.vq_pairs,
cfg.guest_mac(),
Arc::new(cfg.queue_sizes()),
epoll_mgr,
)?))
}
/// Insert or update a vhost-net device into the device manager. If it is a
/// hotplug device, then it will be attached to the hypervisor.
pub fn insert_device(
device_mgr: &mut DeviceManager,
mut ctx: DeviceOpContext,
config: VhostNetDeviceConfigInfo,
) -> Result<(), VhostNetDeviceError> {
if config.num_queues % 2 != 0 {
return Err(VhostNetDeviceError::InvalidQueueNum(config.num_queues));
}
if !cfg!(feature = "hotplug") && ctx.is_hotplug {
return Err(VhostNetDeviceError::UpdateNotAllowedPostBoot);
}
slog::info!(
ctx.logger(),
"add vhost-net device configuration";
"subsystem" => "vhost_net_dev_mgr",
"id" => &config.iface_id,
"host_dev_name" => &config.host_dev_name,
);
let mgr = &mut device_mgr.vhost_net_manager;
let device_index = mgr.info_list.insert_or_update(&config)?;
// If it is a hotplug device, then it will be attached immediately.
if ctx.is_hotplug {
slog::info!(
ctx.logger(),
"attach vhost-net device";
"subsystem" => "vhost_net_dev_mgr",
"id" => &config.iface_id,
"host_dev_name" => &config.host_dev_name,
);
match Self::create_device(&config, &mut ctx) {
Ok(device) => {
let mmio_dev = DeviceManager::create_mmio_virtio_device(
device,
&mut ctx,
config.use_shared_irq.unwrap_or(mgr.use_shared_irq),
config.use_generic_irq.unwrap_or(USE_GENERIC_IRQ),
)
.map_err(VhostNetDeviceError::RegisterNetDevice)?;
ctx.insert_hotplug_mmio_device(&mmio_dev, None)
.map_err(VhostNetDeviceError::DeviceManager)?;
// live-upgrade need save/restore device from info.device.
mgr.info_list[device_index].set_device(mmio_dev);
}
Err(err) => {
mgr.info_list.remove(device_index);
return Err(VhostNetDeviceError::Virtio(err));
}
}
}
Ok(())
}
/// Attach all configured vhost-net device to the virtual machine instance.
pub fn attach_devices(&mut self, ctx: &mut DeviceOpContext) -> Result<(), VhostNetDeviceError> {
for info in self.info_list.iter_mut() {
slog::info!(
ctx.logger(),
"attach vhost-net device";
"subsystem" => "vhost_net_dev_mgr",
"id" => &info.config.iface_id,
"host_dev_name" => &info.config.host_dev_name,
);
let device = Self::create_device(&info.config, ctx)
.map_err(VhostNetDeviceError::CreateNetDevice)?;
let mmio_dev = DeviceManager::create_mmio_virtio_device(
device,
ctx,
info.config.use_shared_irq.unwrap_or(self.use_shared_irq),
info.config.use_generic_irq.unwrap_or(USE_GENERIC_IRQ),
)
.map_err(VhostNetDeviceError::RegisterNetDevice)?;
info.set_device(mmio_dev);
}
Ok(())
}
/// Remove all vhost-net devices.
pub fn remove_devices(&mut self, ctx: &mut DeviceOpContext) -> Result<(), DeviceMgrError> {
while let Some(mut info) = self.info_list.pop() {
slog::info!(
ctx.logger(),
"remove virtio-net device: {}",
info.config.iface_id
);
if let Some(device) = info.device.take() {
DeviceManager::destroy_mmio_virtio_device(device, ctx)?;
}
}
Ok(())
}
}
impl Default for VhostNetDeviceMgr {
fn default() -> Self {
Self {
info_list: DeviceConfigInfos::new(),
use_shared_irq: USE_SHARED_IRQ,
}
}
}
#[cfg(test)]
mod tests {
use dbs_utils::net::MacAddr;
use dbs_virtio_devices::Error as VirtioError;
use crate::{
device_manager::{
vhost_net_dev_mgr::{VhostNetDeviceConfigInfo, VhostNetDeviceError, VhostNetDeviceMgr},
DeviceManager, DeviceMgrError, DeviceOpContext,
},
test_utils::tests::create_vm_for_test,
vm::VmConfigInfo,
};
#[test]
fn test_create_vhost_net_device() {
let vm = create_vm_for_test();
let mgr = DeviceManager::new_test_mgr();
let id_1 = String::from("id_1");
let host_dev_name_1 = String::from("dev1");
let guest_mac_1 = "01:23:45:67:89:0a";
let netif_1 = VhostNetDeviceConfigInfo {
iface_id: id_1,
host_dev_name: host_dev_name_1,
num_queues: 2,
vq_pairs: 0,
queue_size: 128,
guest_mac: Some(MacAddr::parse_str(guest_mac_1).unwrap()),
allow_duplicate_mac: false,
use_shared_irq: None,
use_generic_irq: None,
};
// no epoll manager
let mut ctx = DeviceOpContext::new(
None,
&mgr,
None,
None,
false,
Some(VmConfigInfo::default()),
vm.shared_info().clone(),
);
assert!(VhostNetDeviceMgr::create_device(&netif_1, &mut ctx).is_err());
}
#[test]
fn test_attach_vhost_net_device() {
// Init vm for test.
let mut vm = create_vm_for_test();
let device_op_ctx = DeviceOpContext::new(
Some(vm.epoll_manager().clone()),
vm.device_manager(),
Some(vm.vm_as().unwrap().clone()),
vm.vm_address_space().cloned(),
false,
Some(VmConfigInfo::default()),
vm.shared_info().clone(),
);
let id_1 = String::from("id_1");
let host_dev_name_1 = String::from("dev1");
let guest_mac_1 = "01:23:45:67:89:0a";
let netif_1 = VhostNetDeviceConfigInfo {
iface_id: id_1,
host_dev_name: host_dev_name_1,
num_queues: 2,
vq_pairs: 0,
queue_size: 128,
guest_mac: Some(MacAddr::parse_str(guest_mac_1).unwrap()),
allow_duplicate_mac: false,
use_shared_irq: None,
use_generic_irq: None,
};
assert!(
VhostNetDeviceMgr::insert_device(vm.device_manager_mut(), device_op_ctx, netif_1)
.is_ok()
);
assert_eq!(vm.device_manager().vhost_net_manager.info_list.len(), 1);
let mut device_op_ctx = DeviceOpContext::new(
Some(vm.epoll_manager().clone()),
vm.device_manager(),
Some(vm.vm_as().unwrap().clone()),
vm.vm_address_space().cloned(),
false,
Some(VmConfigInfo::default()),
vm.shared_info().clone(),
);
assert!(vm
.device_manager_mut()
.vhost_net_manager
.attach_devices(&mut device_op_ctx)
.is_ok());
}
#[test]
fn test_insert_vhost_net_device() {
let vm = create_vm_for_test();
let mut mgr = DeviceManager::new_test_mgr();
let id_1 = String::from("id_1");
let mut host_dev_name_1 = String::from("dev1");
let mut guest_mac_1 = "01:23:45:67:89:0a";
// Test create.
let mut netif_1 = VhostNetDeviceConfigInfo {
iface_id: id_1,
host_dev_name: host_dev_name_1,
num_queues: 2,
vq_pairs: 0,
queue_size: 128,
guest_mac: Some(MacAddr::parse_str(guest_mac_1).unwrap()),
allow_duplicate_mac: false,
use_shared_irq: None,
use_generic_irq: None,
};
let ctx = DeviceOpContext::new(
None,
&mgr,
None,
None,
false,
Some(VmConfigInfo::default()),
vm.shared_info().clone(),
);
assert!(VhostNetDeviceMgr::insert_device(&mut mgr, ctx, netif_1.clone()).is_ok());
assert_eq!(mgr.vhost_net_manager.info_list.len(), 1);
// Test update mac address (this test does not modify the tap).
guest_mac_1 = "01:23:45:67:89:0b";
netif_1.guest_mac = Some(MacAddr::parse_str(guest_mac_1).unwrap());
let ctx = DeviceOpContext::new(
None,
&mgr,
None,
None,
false,
Some(VmConfigInfo::default()),
vm.shared_info().clone(),
);
assert!(VhostNetDeviceMgr::insert_device(&mut mgr, ctx, netif_1.clone()).is_ok());
assert_eq!(mgr.vhost_net_manager.info_list.len(), 1);
// Test update host_dev_name (the tap will be updated).
host_dev_name_1 = String::from("dev2");
netif_1.host_dev_name = host_dev_name_1;
let ctx = DeviceOpContext::new(
None,
&mgr,
None,
None,
false,
Some(VmConfigInfo::default()),
vm.shared_info().clone(),
);
assert!(VhostNetDeviceMgr::insert_device(&mut mgr, ctx, netif_1).is_ok());
assert_eq!(mgr.vhost_net_manager.info_list.len(), 1);
}
#[test]
fn test_vhost_net_insert_error_cases() {
let vm = create_vm_for_test();
let mut mgr = DeviceManager::new_test_mgr();
let guest_mac_1 = "01:23:45:67:89:0a";
let guest_mac_2 = "11:45:45:67:89:0b";
let ctx = DeviceOpContext::new(
None,
&mgr,
None,
None,
false,
Some(VmConfigInfo::default()),
vm.shared_info().clone(),
);
// invalid queue num
let mut netif_1 = VhostNetDeviceConfigInfo {
iface_id: String::from("id_1"),
host_dev_name: String::from("dev_1"),
num_queues: 1,
vq_pairs: 0,
queue_size: 128,
guest_mac: Some(MacAddr::parse_str(guest_mac_1).unwrap()),
allow_duplicate_mac: false,
use_shared_irq: None,
use_generic_irq: None,
};
let res = VhostNetDeviceMgr::insert_device(&mut mgr, ctx, netif_1.clone());
if let Err(VhostNetDeviceError::InvalidQueueNum(1)) = res {
assert_eq!(mgr.vhost_net_manager.info_list.len(), 0);
} else {
panic!();
}
// Adding the first valid network config.
netif_1.num_queues = 2;
let ctx = DeviceOpContext::new(
None,
&mgr,
None,
None,
false,
Some(VmConfigInfo::default()),
vm.shared_info().clone(),
);
assert!(VhostNetDeviceMgr::insert_device(&mut mgr, ctx, netif_1.clone()).is_ok());
assert_eq!(mgr.vhost_net_manager.info_list.len(), 1);
// Error Cases for CREATE
// Error Case: Add new network config with the same host_dev_name
netif_1.iface_id = String::from("id_2");
netif_1.guest_mac = Some(MacAddr::parse_str(guest_mac_2).unwrap());
let ctx = DeviceOpContext::new(
None,
&mgr,
None,
None,
false,
Some(VmConfigInfo::default()),
vm.shared_info().clone(),
);
let res = VhostNetDeviceMgr::insert_device(&mut mgr, ctx, netif_1.clone());
if let Err(VhostNetDeviceError::HostDeviceNameInUse(_)) = res {
assert_eq!(mgr.vhost_net_manager.info_list.len(), 1);
} else {
panic!();
}
// Error Cases for CREATE
// Error Case: Add new network config with the same guest_address
netif_1.iface_id = String::from("id_2");
netif_1.host_dev_name = String::from("dev_2");
netif_1.guest_mac = Some(MacAddr::parse_str(guest_mac_1).unwrap());
let ctx = DeviceOpContext::new(
None,
&mgr,
None,
None,
false,
Some(VmConfigInfo::default()),
vm.shared_info().clone(),
);
let res = VhostNetDeviceMgr::insert_device(&mut mgr, ctx, netif_1);
if let Err(VhostNetDeviceError::GuestMacAddressInUse(_)) = res {
assert_eq!(mgr.vhost_net_manager.info_list.len(), 1);
} else {
panic!();
}
// Adding the second valid network config.
let mut netif_2 = VhostNetDeviceConfigInfo {
iface_id: String::from("id_2"),
host_dev_name: String::from("dev_2"),
num_queues: 2,
vq_pairs: 0,
queue_size: 128,
guest_mac: Some(MacAddr::parse_str(guest_mac_2).unwrap()),
allow_duplicate_mac: false,
use_shared_irq: None,
use_generic_irq: None,
};
let ctx = DeviceOpContext::new(
None,
&mgr,
None,
None,
false,
Some(VmConfigInfo::default()),
vm.shared_info().clone(),
);
assert!(VhostNetDeviceMgr::insert_device(&mut mgr, ctx, netif_2.clone()).is_ok());
assert_eq!(mgr.vhost_net_manager.info_list.len(), 2);
// Error Cases for UPDATE
// Error Case: update netif_2 network config with the same host_dev_name as netif_1
netif_2.host_dev_name = String::from("dev_1");
let ctx = DeviceOpContext::new(
None,
&mgr,
None,
None,
false,
Some(VmConfigInfo::default()),
vm.shared_info().clone(),
);
let res = VhostNetDeviceMgr::insert_device(&mut mgr, ctx, netif_2.clone());
if let Err(VhostNetDeviceError::HostDeviceNameInUse(_)) = res {
assert_eq!(mgr.vhost_net_manager.info_list.len(), 2);
} else {
panic!();
}
// Error Cases for UPDATE
// Error Case: update netif_2 network config with the same guest_address as netif_
netif_2.guest_mac = Some(MacAddr::parse_str(guest_mac_1).unwrap());
let ctx = DeviceOpContext::new(
None,
&mgr,
None,
None,
false,
Some(VmConfigInfo::default()),
vm.shared_info().clone(),
);
let res = VhostNetDeviceMgr::insert_device(&mut mgr, ctx, netif_2);
if let Err(VhostNetDeviceError::GuestMacAddressInUse(_)) = res {
assert_eq!(mgr.vhost_net_manager.info_list.len(), 2);
} else {
panic!();
}
// Adding the third valid network config with same mac.
let netif_3 = VhostNetDeviceConfigInfo {
iface_id: String::from("id_3"),
host_dev_name: String::from("dev_3"),
num_queues: 2,
vq_pairs: 0,
queue_size: 128,
guest_mac: Some(MacAddr::parse_str(guest_mac_1).unwrap()),
allow_duplicate_mac: true,
use_shared_irq: None,
use_generic_irq: None,
};
let ctx = DeviceOpContext::new(
None,
&mgr,
None,
None,
false,
Some(VmConfigInfo::default()),
vm.shared_info().clone(),
);
assert!(VhostNetDeviceMgr::insert_device(&mut mgr, ctx, netif_3).is_ok());
assert_eq!(mgr.vhost_net_manager.info_list.len(), 3);
}
#[test]
fn test_vhost_net_error_display() {
let err = VhostNetDeviceError::InvalidQueueNum(0);
let _ = format!("{}{:?}", err, err);
let err = VhostNetDeviceError::DeviceManager(DeviceMgrError::GetDeviceResource);
let _ = format!("{}{:?}", err, err);
let err = VhostNetDeviceError::DeviceIdAlreadyExist(String::from("1"));
let _ = format!("{}{:?}", err, err);
let err = VhostNetDeviceError::GuestMacAddressInUse(String::from("1"));
let _ = format!("{}{:?}", err, err);
let err = VhostNetDeviceError::HostDeviceNameInUse(String::from("1"));
let _ = format!("{}{:?}", err, err);
let err = VhostNetDeviceError::Virtio(VirtioError::DescriptorChainTooShort);
let _ = format!("{}{:?}", err, err);
let err = VhostNetDeviceError::UpdateNotAllowedPostBoot;
let _ = format!("{}{:?}", err, err);
}
}

View File

@@ -26,9 +26,9 @@ use crate::get_bucket_update;
use super::DbsMmioV2Device;
/// Default number of virtio queues, one rx/tx pair.
pub const NUM_QUEUES: usize = 2;
pub const DEFAULT_NUM_QUEUES: usize = 2;
/// Default size of virtio queues.
pub const QUEUE_SIZE: u16 = 256;
pub const DEFAULT_QUEUE_SIZE: u16 = 256;
// The flag of whether to use the shared irq.
const USE_SHARED_IRQ: bool = true;
// The flag of whether to use the generic irq.
@@ -123,6 +123,7 @@ impl VirtioNetDeviceConfigUpdateInfo {
}
/// Configuration information for virtio net devices.
/// TODO: https://github.com/kata-containers/kata-containers/issues/8382.
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, Default)]
pub struct VirtioNetDeviceConfigInfo {
/// ID of the guest network interface.
@@ -163,12 +164,12 @@ impl VirtioNetDeviceConfigInfo {
pub fn queue_sizes(&self) -> Vec<u16> {
let mut queue_size = self.queue_size;
if queue_size == 0 {
queue_size = QUEUE_SIZE;
queue_size = DEFAULT_QUEUE_SIZE;
}
let num_queues = if self.num_queues > 0 {
self.num_queues
} else {
NUM_QUEUES
DEFAULT_NUM_QUEUES
};
(0..num_queues).map(|_| queue_size).collect::<Vec<u16>>()

View File

@@ -193,6 +193,11 @@ pub enum StartMicroVmError {
/// Virtio-balloon errors.
#[error("virtio-balloon errors: {0}")]
BalloonDeviceError(#[source] device_manager::balloon_dev_mgr::BalloonDeviceError),
/// Vhost-net device errors.
#[cfg(feature = "vhost-net")]
#[error("vhost-net errors: {0:?}")]
VhostNetDeviceError(#[source] device_manager::vhost_net_dev_mgr::VhostNetDeviceError),
}
/// Errors associated with starting the instance.

View File

@@ -797,6 +797,7 @@ dependencies = [
"serde_json",
"thiserror",
"threadpool",
"vhost",
"virtio-bindings",
"virtio-queue",
"vm-memory",
@@ -4049,6 +4050,18 @@ version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "vhost"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6769e8dbf5276b4376439fbf36bb880d203bf614bf7ef444198edc24b5a9f35"
dependencies = [
"bitflags 1.3.2",
"libc",
"vm-memory",
"vmm-sys-util 0.11.1",
]
[[package]]
name = "virt_container"
version = "0.1.0"

View File

@@ -36,7 +36,7 @@ kata-types = { path = "../../../libs/kata-types" }
logging = { path = "../../../libs/logging" }
shim-interface = { path = "../../../libs/shim-interface" }
dragonball = { path = "../../../dragonball", features = ["atomic-guest-memory", "virtio-vsock", "hotplug", "virtio-blk", "virtio-net", "virtio-fs", "dbs-upcall"] }
dragonball = { path = "../../../dragonball", features = ["atomic-guest-memory", "virtio-vsock", "hotplug", "virtio-blk", "virtio-net", "virtio-fs", "vhost-net", "dbs-upcall"] }
ch-config = { path = "ch-config", optional = true }
tests_utils = { path = "../../tests/utils" }

View File

@@ -413,7 +413,7 @@ impl TryFrom<ShareFsSettings> for FsConfig {
#[cfg(test)]
mod tests {
use super::*;
use crate::Address;
use crate::{Address, Backend};
#[test]
fn test_networkconfig_to_netconfig() {
@@ -424,6 +424,10 @@ mod tests {
queue_num: 2,
guest_mac: None,
index: 1,
allow_duplicate_mac: false,
use_generic_irq: None,
use_shared_irq: None,
backend: Backend::default(),
};
let net = NetConfig::try_from(cfg.clone());

View File

@@ -307,11 +307,12 @@ impl DeviceManager {
}
DeviceConfig::NetworkCfg(config) => {
// try to find the device, found and just return id.
if let Some(dev_id_matched) = self.find_device(config.host_dev_name.clone()).await {
let host_path = config.host_dev_name.as_str();
if let Some(dev_id_matched) = self.find_device(host_path.to_owned()).await {
info!(
sl!(),
"network device with path:{:?} found. return network device id: {:?}",
config.host_dev_name.clone(),
host_path,
dev_id_matched
);

View File

@@ -23,7 +23,7 @@ pub use virtio_fs::{
ShareFsDevice, ShareFsDeviceConfig, ShareFsMountConfig, ShareFsMountDevice, ShareFsMountType,
ShareFsOperation,
};
pub use virtio_net::{Address, NetworkConfig, NetworkDevice};
pub use virtio_net::{Address, Backend, NetworkConfig, NetworkDevice};
pub use virtio_vsock::{
HybridVsockConfig, HybridVsockDevice, VsockConfig, VsockDevice, DEFAULT_GUEST_VSOCK_CID,
};

View File

@@ -9,10 +9,8 @@ use std::fmt;
use anyhow::{Context, Result};
use async_trait::async_trait;
use crate::{
device::{Device, DeviceType},
Hypervisor as hypervisor,
};
use crate::device::{Device, DeviceType};
use crate::Hypervisor as hypervisor;
#[derive(Clone)]
pub struct Address(pub [u8; 6]);
@@ -28,25 +26,36 @@ impl fmt::Debug for Address {
}
}
#[derive(Clone, Debug, Default)]
pub enum Backend {
#[default]
Virtio,
Vhost,
}
#[derive(Clone, Debug, Default)]
pub struct NetworkConfig {
/// for detach, now it's default value 0.
pub index: u64,
/// Network device backend
pub backend: Backend,
/// Host level path for the guest network interface.
pub host_dev_name: String,
/// Guest iface name for the guest network interface.
pub virt_iface_name: String,
/// Guest MAC address.
pub guest_mac: Option<Address>,
/// Virtio queue size
pub queue_size: usize,
/// Virtio queue num
pub queue_num: usize,
/// Use shared irq
pub use_shared_irq: Option<bool>,
/// Use generic irq
pub use_generic_irq: Option<bool>,
/// Allow duplicate mac
pub allow_duplicate_mac: bool,
}
#[derive(Clone, Debug, Default)]

View File

@@ -7,14 +7,10 @@
use std::path::PathBuf;
use anyhow::{anyhow, Context, Result};
use dbs_utils::net::MacAddr;
use dragonball::{
api::v1::{
BlockDeviceConfigInfo, FsDeviceConfigInfo, FsMountConfigInfo, VirtioNetDeviceConfigInfo,
VsockDeviceConfigInfo,
},
device_manager::blk_dev_mgr::BlockDeviceType,
use dragonball::api::v1::{
BlockDeviceConfigInfo, FsDeviceConfigInfo, FsMountConfigInfo, VsockDeviceConfigInfo,
};
use dragonball::device_manager::blk_dev_mgr::BlockDeviceType;
use super::DragonballInner;
use crate::{
@@ -85,7 +81,7 @@ impl DragonballInner {
// Dragonball doesn't support remove network device, just print message.
info!(
sl!(),
"dragonball remove network device: {:?}.", network.config.virt_iface_name
"dragonball remove network device: {:?}.", network.config
);
Ok(())
@@ -204,25 +200,8 @@ impl DragonballInner {
}
fn add_net_device(&mut self, config: &NetworkConfig) -> Result<()> {
let iface_cfg = VirtioNetDeviceConfigInfo {
iface_id: config.virt_iface_name.clone(),
host_dev_name: config.host_dev_name.clone(),
guest_mac: match &config.guest_mac {
Some(mac) => MacAddr::from_bytes(&mac.0).ok(),
None => None,
},
num_queues: config.queue_num,
queue_size: config.queue_size as u16,
..Default::default()
};
info!(
sl!(),
"add {} endpoint to {}", iface_cfg.host_dev_name, iface_cfg.iface_id
);
self.vmm_instance
.insert_network_device(iface_cfg)
.insert_network_device(config.into())
.context("insert network device")
}

View File

@@ -16,12 +16,17 @@ use std::sync::Arc;
use anyhow::{Context, Result};
use async_trait::async_trait;
use dbs_utils::net::MacAddr as DragonballMacAddr;
use dragonball::api::v1::{
Backend as DragonballBackend, NetworkInterfaceConfig as DragonballNetworkConfig,
VirtioConfig as DragonballVirtioConfig,
};
use kata_types::capabilities::Capabilities;
use kata_types::config::hypervisor::Hypervisor as HypervisorConfig;
use tokio::sync::RwLock;
use tracing::instrument;
use crate::{DeviceType, Hypervisor, VcpuThreadIds};
use crate::{Backend, DeviceType, Hypervisor, NetworkConfig, VcpuThreadIds};
pub struct Dragonball {
inner: Arc<RwLock<DragonballInner>>,
@@ -190,3 +195,42 @@ impl Persist for Dragonball {
})
}
}
impl From<NetworkConfig> for DragonballNetworkConfig {
fn from(value: NetworkConfig) -> Self {
let r = &value;
r.into()
}
}
impl From<&NetworkConfig> for DragonballNetworkConfig {
fn from(value: &NetworkConfig) -> Self {
let virtio_config = DragonballVirtioConfig {
iface_id: value.virt_iface_name.clone(),
host_dev_name: value.host_dev_name.clone(),
// TODO(justxuewei): rx_rate_limiter is not supported, see:
// https://github.com/kata-containers/kata-containers/issues/8327.
rx_rate_limiter: None,
// TODO(justxuewei): tx_rate_limiter is not supported, see:
// https://github.com/kata-containers/kata-containers/issues/8327.
tx_rate_limiter: None,
allow_duplicate_mac: value.allow_duplicate_mac,
};
let backend = match value.backend {
Backend::Virtio => DragonballBackend::Virtio(virtio_config),
Backend::Vhost => DragonballBackend::Vhost(virtio_config),
};
Self {
num_queues: Some(value.queue_num),
queue_size: Some(value.queue_size as u16),
backend,
guest_mac: value.guest_mac.clone().map(|mac| {
// We are safety since mac address is checked by endpoints.
DragonballMacAddr::from_bytes(&mac.0).unwrap()
}),
use_shared_irq: value.use_shared_irq,
use_generic_irq: value.use_generic_irq,
}
}
}

View File

@@ -16,7 +16,7 @@ use crossbeam_channel::{unbounded, Receiver, Sender};
use dragonball::{
api::v1::{
BlockDeviceConfigInfo, BootSourceConfig, FsDeviceConfigInfo, FsMountConfigInfo,
InstanceInfo, InstanceState, VcpuResizeInfo, VirtioNetDeviceConfigInfo, VmmAction,
InstanceInfo, InstanceState, NetworkInterfaceConfig, VcpuResizeInfo, VmmAction,
VmmActionError, VmmData, VmmRequest, VmmResponse, VmmService, VsockDeviceConfigInfo,
},
vm::VmConfigInfo,
@@ -216,7 +216,7 @@ impl VmmInstance {
Ok(())
}
pub fn insert_network_device(&self, net_cfg: VirtioNetDeviceConfigInfo) -> Result<()> {
pub fn insert_network_device(&self, net_cfg: NetworkInterfaceConfig) -> Result<()> {
self.handle_request_with_retry(Request::Sync(VmmAction::InsertNetworkDevice(
net_cfg.clone(),
)))

View File

@@ -4,28 +4,19 @@
// SPDX-License-Identifier: Apache-2.0
//
use std::{
io::{self, Error},
sync::Arc,
};
use std::io::{self, Error};
use std::sync::Arc;
use anyhow::{Context, Result};
use async_trait::async_trait;
use hypervisor::device::device_manager::{do_handle_device, DeviceManager};
use hypervisor::device::driver::NetworkConfig;
use hypervisor::device::{DeviceConfig, DeviceType};
use hypervisor::{Backend, Hypervisor, NetworkDevice};
use tokio::sync::RwLock;
use hypervisor::{
device::{
device_manager::{do_handle_device, DeviceManager},
driver::NetworkConfig,
DeviceConfig, DeviceType,
},
Hypervisor, NetworkDevice,
};
use super::{
endpoint_persist::{EndpointState, IpVlanEndpointState},
Endpoint,
};
use super::endpoint_persist::{EndpointState, IpVlanEndpointState};
use super::Endpoint;
use crate::network::{network_model::TC_FILTER_NET_MODEL_STR, utils, NetworkPair};
// IPVlanEndpoint is the endpoint bridged to VM
@@ -66,6 +57,7 @@ impl IPVlanEndpoint {
Ok(NetworkConfig {
host_dev_name: iface.name.clone(),
virt_iface_name: self.net_pair.virt_iface.name.clone(),
backend: Backend::Virtio,
guest_mac: Some(guest_mac),
..Default::default()
})

View File

@@ -4,28 +4,19 @@
// SPDX-License-Identifier: Apache-2.0
//
use std::{
io::{self, Error},
sync::Arc,
};
use std::io::{self, Error};
use std::sync::Arc;
use anyhow::{Context, Result};
use async_trait::async_trait;
use hypervisor::device::device_manager::{do_handle_device, DeviceManager};
use hypervisor::device::driver::NetworkConfig;
use hypervisor::device::{DeviceConfig, DeviceType};
use hypervisor::{Backend, Hypervisor, NetworkDevice};
use tokio::sync::RwLock;
use hypervisor::{
device::{
device_manager::{do_handle_device, DeviceManager},
driver::NetworkConfig,
DeviceConfig, DeviceType,
},
Hypervisor, NetworkDevice,
};
use super::{
endpoint_persist::{EndpointState, MacvlanEndpointState},
Endpoint,
};
use super::endpoint_persist::{EndpointState, MacvlanEndpointState};
use super::Endpoint;
use crate::network::{utils, NetworkPair};
#[derive(Debug)]
@@ -65,6 +56,7 @@ impl MacVlanEndpoint {
Ok(NetworkConfig {
host_dev_name: iface.name.clone(),
virt_iface_name: self.net_pair.virt_iface.name.clone(),
backend: Backend::Virtio,
guest_mac: Some(guest_mac),
..Default::default()
})

View File

@@ -10,7 +10,7 @@ use anyhow::{Context, Result};
use async_trait::async_trait;
use hypervisor::device::device_manager::{do_handle_device, DeviceManager};
use hypervisor::device::{DeviceConfig, DeviceType};
use hypervisor::{Hypervisor, NetworkConfig, NetworkDevice};
use hypervisor::{Backend, Hypervisor, NetworkConfig, NetworkDevice};
use tokio::sync::RwLock;
use super::endpoint_persist::TapEndpointState;
@@ -76,6 +76,7 @@ impl TapEndpoint {
Ok(NetworkConfig {
host_dev_name: self.tap_iface.name.clone(),
virt_iface_name: self.name.clone(),
backend: Backend::Virtio,
guest_mac: Some(guest_mac),
queue_num: self.queue_num,
queue_size: self.queue_size,

View File

@@ -4,28 +4,19 @@
// SPDX-License-Identifier: Apache-2.0
//
use std::{
io::{self, Error},
sync::Arc,
};
use std::io::{self, Error};
use std::sync::Arc;
use anyhow::{Context, Result};
use async_trait::async_trait;
use hypervisor::device::device_manager::{do_handle_device, DeviceManager};
use hypervisor::device::driver::NetworkConfig;
use hypervisor::device::{DeviceConfig, DeviceType};
use hypervisor::{Backend, Hypervisor, NetworkDevice};
use tokio::sync::RwLock;
use hypervisor::{
device::{
device_manager::{do_handle_device, DeviceManager},
driver::NetworkConfig,
DeviceConfig, DeviceType,
},
Hypervisor, NetworkDevice,
};
use super::{
endpoint_persist::{EndpointState, VethEndpointState},
Endpoint,
};
use super::endpoint_persist::{EndpointState, VethEndpointState};
use super::Endpoint;
use crate::network::{utils, NetworkPair};
#[derive(Debug)]
@@ -65,6 +56,7 @@ impl VethEndpoint {
Ok(NetworkConfig {
host_dev_name: iface.name.clone(),
virt_iface_name: self.net_pair.virt_iface.name.clone(),
backend: Backend::Virtio,
guest_mac: Some(guest_mac),
..Default::default()
})

View File

@@ -4,29 +4,21 @@
// SPDX-License-Identifier: Apache-2.0
//
use std::{
io::{self, Error},
sync::Arc,
};
use std::io::{self, Error};
use std::sync::Arc;
use anyhow::{Context, Result};
use async_trait::async_trait;
use hypervisor::device::device_manager::{do_handle_device, DeviceManager};
use hypervisor::device::driver::NetworkConfig;
use hypervisor::device::{DeviceConfig, DeviceType};
use hypervisor::{Backend, Hypervisor, NetworkDevice};
use tokio::sync::RwLock;
use hypervisor::{
device::{
device_manager::{do_handle_device, DeviceManager},
driver::NetworkConfig,
DeviceConfig, DeviceType,
},
Hypervisor, NetworkDevice,
};
use super::{
endpoint_persist::{EndpointState, VlanEndpointState},
Endpoint,
};
use crate::network::{network_model::TC_FILTER_NET_MODEL_STR, utils, NetworkPair};
use super::endpoint_persist::{EndpointState, VlanEndpointState};
use super::Endpoint;
use crate::network::network_model::TC_FILTER_NET_MODEL_STR;
use crate::network::{utils, NetworkPair};
#[derive(Debug)]
pub struct VlanEndpoint {
@@ -64,6 +56,7 @@ impl VlanEndpoint {
Ok(NetworkConfig {
host_dev_name: iface.name.clone(),
virt_iface_name: self.net_pair.virt_iface.name.clone(),
backend: Backend::Virtio,
guest_mac: Some(guest_mac),
..Default::default()
})