virtio-devices: net: report link up in config status

Expose `VIRTIO_NET_S_LINK_UP` through the virtio-net config status field
when `VIRTIO_NET_F_STATUS` was negotiated.

This makes the guest-visible status bits reflect the device runtime
state and prepares the config status path used by later post-migration
announce handling.

On-behalf-of: SAP sebastian.eydam@sap.com
Signed-off-by: Sebastian Eydam <sebastian.eydam@cyberus-technology.de>
This commit is contained in:
Sebastian Eydam
2026-04-07 14:39:30 +02:00
committed by Rob Bradford
parent f5967cf287
commit e17c0be127
2 changed files with 144 additions and 14 deletions

View File

@@ -487,6 +487,7 @@ impl Net {
}
avail_features |= 1 << VIRTIO_NET_F_CTRL_VQ;
avail_features |= 1 << VIRTIO_NET_F_STATUS;
let queue_num = num_queues + 1;
let mut config = VirtioNetConfig::default();
@@ -639,6 +640,17 @@ impl Net {
}
}
/// Compute the guest-visible virtio-net status field.
fn guest_visible_status(&self) -> u16 {
let mut status = 0;
if self.common.feature_acked(VIRTIO_NET_F_STATUS.into()) {
status |= VIRTIO_NET_S_LINK_UP as u16;
}
status
}
#[cfg(fuzzing)]
pub fn wait_for_epoll_threads(&mut self) {
self.common.wait_for_epoll_threads();
@@ -663,7 +675,9 @@ impl VirtioDevice for Net {
}
fn read_config(&self, offset: u64, data: &mut [u8]) {
self.read_config_from_slice(self.config.as_slice(), offset, data);
let mut config = self.config;
config.status = self.guest_visible_status();
self.read_config_from_slice(config.as_slice(), offset, data);
}
fn activate(&mut self, context: ActivationContext) -> ActivateResult {
@@ -857,3 +871,52 @@ impl Snapshottable for Net {
}
impl Transportable for Net {}
impl Migratable for Net {}
#[cfg(test)]
mod unit_tests {
use std::mem::size_of;
use seccompiler::SeccompAction;
use virtio_bindings::virtio_net::{VIRTIO_NET_F_STATUS, VIRTIO_NET_S_LINK_UP};
use vmm_sys_util::eventfd::EventFd;
use super::*;
fn test_net(acked_features: u64) -> Net {
Net {
common: VirtioCommon {
acked_features,
..Default::default()
},
id: "test-net".to_string(),
taps: Vec::new(),
config: VirtioNetConfig::default(),
counters: NetCounters::default(),
seccomp_action: SeccompAction::Allow,
rate_limiter_config: None,
exit_evt: EventFd::new(libc::EFD_NONBLOCK).unwrap(),
device_status: Arc::new(AtomicU8::new(0)),
}
}
const STATUS_OFFSET: usize = std::mem::offset_of!(VirtioNetConfig, status);
fn read_status(device: &Net) -> u16 {
let mut data = vec![0; size_of::<VirtioNetConfig>()];
device.read_config(0, &mut data);
u16::from_le_bytes(
data[STATUS_OFFSET..STATUS_OFFSET + size_of::<u16>()]
.try_into()
.unwrap(),
)
}
#[test]
fn test_status_feature_reports_link_up() {
// The current implementation should always report "link up" if
// VIRTIO_NET_F_STATUS has been negotiated.
let net = test_net(1 << VIRTIO_NET_F_STATUS);
assert_eq!(read_status(&net), VIRTIO_NET_S_LINK_UP as u16);
}
}

View File

@@ -14,7 +14,8 @@ use virtio_bindings::virtio_net::{
VIRTIO_NET_F_CSUM, VIRTIO_NET_F_CTRL_VQ, VIRTIO_NET_F_GUEST_CSUM, VIRTIO_NET_F_GUEST_ECN,
VIRTIO_NET_F_GUEST_TSO4, VIRTIO_NET_F_GUEST_TSO6, VIRTIO_NET_F_GUEST_UFO,
VIRTIO_NET_F_HOST_ECN, VIRTIO_NET_F_HOST_TSO4, VIRTIO_NET_F_HOST_TSO6, VIRTIO_NET_F_HOST_UFO,
VIRTIO_NET_F_MAC, VIRTIO_NET_F_MRG_RXBUF, VIRTIO_NET_F_MTU,
VIRTIO_NET_F_MAC, VIRTIO_NET_F_MRG_RXBUF, VIRTIO_NET_F_MTU, VIRTIO_NET_F_STATUS,
VIRTIO_NET_S_LINK_UP,
};
use virtio_bindings::virtio_ring::VIRTIO_RING_F_EVENT_IDX;
use virtio_queue::QueueT;
@@ -86,10 +87,10 @@ impl Net {
) = if let Some(state) = state {
info!("Restoring vhost-user-net {id}");
// The backend acknowledged features must not contain
// VIRTIO_NET_F_MAC since we don't expect the backend
// to handle it.
let backend_acked_features = state.acked_features & !(1 << VIRTIO_NET_F_MAC);
// The backend acknowledged features must not contain frontend-only
// bits since we don't expect the backend to handle them.
let backend_acked_features =
state.acked_features & !((1 << VIRTIO_NET_F_MAC) | (1 << VIRTIO_NET_F_STATUS));
vu.set_protocol_features_vhost_user(
backend_acked_features,
@@ -177,9 +178,9 @@ impl Net {
num_queues += 1;
}
// Make sure the virtio feature to set the MAC address is exposed to
// the guest, even if it hasn't been negotiated with the backend.
acked_features |= 1 << VIRTIO_NET_F_MAC;
// Make sure frontend-owned config-space features stay exposed to
// the guest, even if they are not negotiated with the backend.
acked_features |= (1 << VIRTIO_NET_F_MAC) | (1 << VIRTIO_NET_F_STATUS);
(
acked_features,
@@ -227,6 +228,21 @@ impl Net {
fn state(&self) -> result::Result<State, MigratableError> {
self.vu_common.state(self.config)
}
/// Compute the guest-visible virtio-net status field.
fn guest_visible_status(&self) -> u16 {
let mut status = 0;
if self
.vu_common
.virtio_common
.feature_acked(VIRTIO_NET_F_STATUS.into())
{
status |= VIRTIO_NET_S_LINK_UP as u16;
}
status
}
}
impl Drop for Net {
@@ -257,7 +273,9 @@ impl VirtioDevice for Net {
}
fn read_config(&self, offset: u64, data: &mut [u8]) {
self.read_config_from_slice(self.config.as_slice(), offset, data);
let mut config = self.config;
config.status = self.guest_visible_status();
self.read_config_from_slice(config.as_slice(), offset, data);
}
fn activate(&mut self, context: ActivationContext) -> ActivateResult {
@@ -321,10 +339,10 @@ impl VirtioDevice for Net {
let backend_req_handler: Option<FrontendReqHandler<BackendReqHandler>> = None;
// The backend acknowledged features must not contain VIRTIO_NET_F_MAC
// since we don't expect the backend to handle it.
let backend_acked_features =
self.vu_common.virtio_common.acked_features & !(1 << VIRTIO_NET_F_MAC);
// The backend acknowledged features must not contain frontend-only
// bits since we don't expect the backend to handle them.
let backend_acked_features = self.vu_common.virtio_common.acked_features
& !((1 << VIRTIO_NET_F_MAC) | (1 << VIRTIO_NET_F_STATUS));
// Run a dedicated thread for handling potential reconnections with
// the backend.
@@ -416,3 +434,52 @@ impl Migratable for Net {
self.vu_common.complete_migration()
}
}
#[cfg(test)]
mod unit_tests {
use std::mem::size_of;
use seccompiler::SeccompAction;
use virtio_bindings::virtio_net::{VIRTIO_NET_F_STATUS, VIRTIO_NET_S_LINK_UP};
use vmm_sys_util::eventfd::EventFd;
use super::*;
fn test_net(acked_features: u64) -> Net {
Net {
vu_common: VhostUserCommon {
virtio_common: VirtioCommon {
acked_features,
..Default::default()
},
..Default::default()
},
id: "test-vu-net".to_string(),
config: VirtioNetConfig::default(),
seccomp_action: SeccompAction::Allow,
exit_evt: EventFd::new(libc::EFD_NONBLOCK).unwrap(),
access_platform_enabled: false,
}
}
const STATUS_OFFSET: usize = std::mem::offset_of!(VirtioNetConfig, status);
fn read_status(device: &Net) -> u16 {
let mut data = vec![0; size_of::<VirtioNetConfig>()];
device.read_config(0, &mut data);
u16::from_le_bytes(
data[STATUS_OFFSET..STATUS_OFFSET + size_of::<u16>()]
.try_into()
.unwrap(),
)
}
#[test]
fn test_status_feature_reports_link_up() {
// The current implementation should always report "link up" if
// VIRTIO_NET_F_STATUS has been negotiated.
let net = test_net(1 << VIRTIO_NET_F_STATUS);
assert_eq!(read_status(&net), VIRTIO_NET_S_LINK_UP as u16);
}
}