From f09ab0898d8647fd67e18bffa52dcc1ddae7724a Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Fri, 1 May 2026 17:44:58 +0100 Subject: [PATCH] vmm: config: Reject rate limiting with vhost_user Rate limiting is implemented in the virtio device layer and does not apply to vhost-user devices which delegate I/O handling to an external process. Add validation to reject configurations where vhost_user is enabled along with rate limiting options (bw_size, ops_size, or rate_limit_group) for both disk and network devices. This prevents users from mistakenly configuring rate limiting that would be silently ignored when using vhost-user backends. Signed-off-by: Rob Bradford --- vmm/src/config.rs | 67 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/vmm/src/config.rs b/vmm/src/config.rs index 8e16cfc7e..dc027e2fc 100644 --- a/vmm/src/config.rs +++ b/vmm/src/config.rs @@ -348,6 +348,9 @@ pub enum ValidationError { /// Invalid rate-limiter group #[error("Invalid rate-limiter group")] InvalidRateLimiterGroup, + /// Rate limiting is not supported with vhost-user + #[error("Rate limiting is not supported with vhost-user")] + VhostUserRateLimiterNotSupported, /// The specified I/O port was invalid. It should be provided in hex, such as `0xe9`. #[cfg(target_arch = "x86_64")] #[error("The IO port was not properly provided in hex or a `0x` prefix is missing: {0}")] @@ -1480,6 +1483,14 @@ impl DiskConfig { return Err(ValidationError::IommuNotSupported); } + if self.vhost_user && self.rate_limiter_config.is_some() { + return Err(ValidationError::VhostUserRateLimiterNotSupported); + } + + if self.vhost_user && self.rate_limit_group.is_some() { + return Err(ValidationError::VhostUserRateLimiterNotSupported); + } + if self.rate_limiter_config.is_some() && self.rate_limit_group.is_some() { return Err(ValidationError::InvalidRateLimiterGroup); } @@ -1709,6 +1720,10 @@ impl NetConfig { return Err(ValidationError::IommuNotSupported); } + if self.vhost_user && self.rate_limiter_config.is_some() { + return Err(ValidationError::VhostUserRateLimiterNotSupported); + } + if let Some(mtu) = self.mtu && mtu < virtio_devices::net::MIN_MTU { @@ -2913,6 +2928,11 @@ impl VmConfig { if disk.vhost_user && disk.vhost_socket.is_none() { return Err(ValidationError::VhostUserMissingSocket); } + if disk.vhost_user + && (disk.rate_limiter_config.is_some() || disk.rate_limit_group.is_some()) + { + return Err(ValidationError::VhostUserRateLimiterNotSupported); + } if let Some(rate_limit_group) = &disk.rate_limit_group { if let Some(rate_limit_groups) = &self.rate_limit_groups { if !rate_limit_groups @@ -2938,6 +2958,9 @@ impl VmConfig { if net.vhost_user && !self.backed_by_shared_memory() { return Err(ValidationError::VhostUserRequiresSharedMemory); } + if net.vhost_user && net.rate_limiter_config.is_some() { + return Err(ValidationError::VhostUserRateLimiterNotSupported); + } net.validate(self)?; self.iommu |= net.pci_common.iommu; @@ -5162,6 +5185,50 @@ id=\"{id}\",pci_segment={pci_segment},queue_sizes={queue_sizes}" still_valid_config.memory.shared = true; still_valid_config.validate().unwrap(); + // Test vhost_user with rate limiting for disk + let mut invalid_config = valid_config.clone(); + invalid_config.memory.shared = true; + invalid_config.disks = Some(vec![DiskConfig { + path: None, + vhost_user: true, + vhost_socket: Some("/path/to/sock".to_owned()), + rate_limiter_config: Some(RateLimiterConfig::default()), + ..disk_fixture() + }]); + assert_eq!( + invalid_config.validate(), + Err(ValidationError::VhostUserRateLimiterNotSupported) + ); + + // Test vhost_user with rate_limit_group for disk + let mut invalid_config = valid_config.clone(); + invalid_config.memory.shared = true; + invalid_config.disks = Some(vec![DiskConfig { + path: None, + vhost_user: true, + vhost_socket: Some("/path/to/sock".to_owned()), + rate_limit_group: Some("group0".to_string()), + ..disk_fixture() + }]); + assert_eq!( + invalid_config.validate(), + Err(ValidationError::VhostUserRateLimiterNotSupported) + ); + + // Test vhost_user with rate limiting for net + let mut invalid_config = valid_config.clone(); + invalid_config.memory.shared = true; + invalid_config.net = Some(vec![NetConfig { + vhost_user: true, + vhost_socket: Some("/path/to/sock".to_owned()), + rate_limiter_config: Some(RateLimiterConfig::default()), + ..net_fixture() + }]); + assert_eq!( + invalid_config.validate(), + Err(ValidationError::VhostUserRateLimiterNotSupported) + ); + let mut invalid_config = valid_config.clone(); invalid_config.net = Some(vec![NetConfig { fds: Some(vec![0]),