From e17c0be127caa7184614a240d3ec8a552c6a913b Mon Sep 17 00:00:00 2001 From: Sebastian Eydam Date: Tue, 7 Apr 2026 14:39:30 +0200 Subject: [PATCH] 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 --- virtio-devices/src/net.rs | 65 ++++++++++++++++++- virtio-devices/src/vhost_user/net.rs | 93 ++++++++++++++++++++++++---- 2 files changed, 144 insertions(+), 14 deletions(-) diff --git a/virtio-devices/src/net.rs b/virtio-devices/src/net.rs index 42a8090c2..b26e541e6 100644 --- a/virtio-devices/src/net.rs +++ b/virtio-devices/src/net.rs @@ -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::()]; + device.read_config(0, &mut data); + + u16::from_le_bytes( + data[STATUS_OFFSET..STATUS_OFFSET + size_of::()] + .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); + } +} diff --git a/virtio-devices/src/vhost_user/net.rs b/virtio-devices/src/vhost_user/net.rs index bbcc642a8..58fe99953 100644 --- a/virtio-devices/src/vhost_user/net.rs +++ b/virtio-devices/src/vhost_user/net.rs @@ -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 { 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> = 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::()]; + device.read_config(0, &mut data); + + u16::from_le_bytes( + data[STATUS_OFFSET..STATUS_OFFSET + size_of::()] + .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); + } +}