// Copyright 2019 Intel Corporation. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use std::sync::atomic::AtomicBool; use std::sync::{Arc, Barrier, Mutex}; use std::{result, thread}; use event_monitor::event; use log::{error, info}; use net_util::{CtrlQueue, MacAddr, VirtioNetConfig, build_net_config_space}; use seccompiler::SeccompAction; use vhost::vhost_user::message::{VhostUserProtocolFeatures, VhostUserVirtioFeatures}; use vhost::vhost_user::{FrontendReqHandler, VhostUserFrontend, VhostUserFrontendReqHandler}; 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, }; use virtio_bindings::virtio_ring::VIRTIO_RING_F_EVENT_IDX; use virtio_queue::QueueT; use vm_memory::{ByteValued, GuestMemoryAtomic}; use vm_migration::protocol::MemoryRangeTable; use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable}; use vmm_sys_util::eventfd::EventFd; use crate::seccomp_filters::Thread; use crate::thread_helper::spawn_virtio_thread; use crate::vhost_user::vu_common_ctrl::{VhostUserConfig, VhostUserHandle}; use crate::vhost_user::{DEFAULT_VIRTIO_FEATURES, Error, Result, VhostUserCommon, VhostUserState}; use crate::{ ActivateResult, GuestMemoryMmap, GuestRegionMmap, NetCtrlEpollHandler, VIRTIO_F_ACCESS_PLATFORM, VirtioCommon, VirtioDevice, VirtioDeviceType, VirtioInterrupt, }; const DEFAULT_QUEUE_NUMBER: usize = 2; pub type State = VhostUserState; struct BackendReqHandler {} impl VhostUserFrontendReqHandler for BackendReqHandler {} pub struct Net { vu_common: VhostUserCommon, id: String, config: VirtioNetConfig, guest_memory: Option>, ctrl_queue_epoll_thread: Option>, seccomp_action: SeccompAction, exit_evt: EventFd, iommu: bool, } impl Net { /// Create a new vhost-user-net device #[allow(clippy::too_many_arguments)] pub fn new( id: String, mac_addr: MacAddr, mtu: Option, vu_cfg: VhostUserConfig, server: bool, seccomp_action: SeccompAction, exit_evt: EventFd, iommu: bool, state: Option, offload_tso: bool, offload_ufo: bool, offload_csum: bool, ) -> Result { let mut num_queues = vu_cfg.num_queues; let mut vu = VhostUserHandle::connect_vhost_user(server, &vu_cfg.socket, num_queues as u64, false)?; let ( avail_features, acked_features, acked_protocol_features, vu_num_queues, config, paused, vring_bases, ) = 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); vu.set_protocol_features_vhost_user( backend_acked_features, state.acked_protocol_features, )?; vu.restore_state(&state)?; // If the control queue feature has been negotiated, let's // increase the number of queues. if state.acked_features & (1 << VIRTIO_NET_F_CTRL_VQ) != 0 { num_queues += 1; } ( state.avail_features, state.acked_features, state.acked_protocol_features, state.vu_num_queues, state.config, true, state.vring_bases, ) } else { // Filling device and vring features VMM supports. let mut avail_features = (1 << VIRTIO_NET_F_MRG_RXBUF) | (1 << VIRTIO_NET_F_CTRL_VQ) | DEFAULT_VIRTIO_FEATURES; if mtu.is_some() { avail_features |= 1u64 << VIRTIO_NET_F_MTU; } // Configure TSO/UFO features when hardware checksum offload is enabled. if offload_csum { avail_features |= (1 << VIRTIO_NET_F_CSUM) | (1 << VIRTIO_NET_F_GUEST_CSUM); if offload_tso { avail_features |= (1 << VIRTIO_NET_F_HOST_ECN) | (1 << VIRTIO_NET_F_HOST_TSO4) | (1 << VIRTIO_NET_F_HOST_TSO6) | (1 << VIRTIO_NET_F_GUEST_ECN) | (1 << VIRTIO_NET_F_GUEST_TSO4) | (1 << VIRTIO_NET_F_GUEST_TSO6); } if offload_ufo { avail_features |= (1 << VIRTIO_NET_F_HOST_UFO) | (1 << VIRTIO_NET_F_GUEST_UFO); } } let mut config = VirtioNetConfig::default(); build_net_config_space(&mut config, mac_addr, num_queues, mtu, &mut avail_features); let avail_protocol_features = VhostUserProtocolFeatures::MQ | VhostUserProtocolFeatures::CONFIGURE_MEM_SLOTS | VhostUserProtocolFeatures::REPLY_ACK | VhostUserProtocolFeatures::INFLIGHT_SHMFD | VhostUserProtocolFeatures::LOG_SHMFD | VhostUserProtocolFeatures::DEVICE_STATE; let (mut acked_features, acked_protocol_features) = vu.negotiate_features_vhost_user(avail_features, avail_protocol_features)?; let backend_num_queues = if acked_protocol_features & VhostUserProtocolFeatures::MQ.bits() != 0 { vu.socket_handle() .get_queue_num() .map_err(Error::VhostUserGetQueueMaxNum)? as usize } else { DEFAULT_QUEUE_NUMBER }; if num_queues > backend_num_queues { error!( "vhost-user-net requested too many queues ({num_queues}) since the backend only supports {backend_num_queues}\n" ); return Err(Error::BadQueueNum); } // If the control queue feature has been negotiated, let's increase // the number of queues. let vu_num_queues = num_queues; if acked_features & (1 << VIRTIO_NET_F_CTRL_VQ) != 0 { 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; ( acked_features, // If part of the available features that have been acked, // the PROTOCOL_FEATURES bit must be already set through // the VIRTIO acked features as we know the guest would // never ack it, thus the feature would be lost. acked_features & VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits(), acked_protocol_features, vu_num_queues, config, false, None, ) }; Ok(Net { id, vu_common: VhostUserCommon { virtio_common: VirtioCommon { device_type: VirtioDeviceType::Net as u32, queue_sizes: vec![vu_cfg.queue_size; num_queues], avail_features, acked_features, paused_sync: Some(Arc::new(Barrier::new(2))), min_queues: DEFAULT_QUEUE_NUMBER as u16, paused: Arc::new(AtomicBool::new(paused)), ..Default::default() }, vu: Some(Arc::new(Mutex::new(vu))), acked_protocol_features, socket_path: vu_cfg.socket, vu_num_queues, server, vring_bases, ..Default::default() }, config, guest_memory: None, ctrl_queue_epoll_thread: None, seccomp_action, exit_evt, iommu, }) } fn state(&self) -> std::result::Result { self.vu_common.state(self.config) } } impl Drop for Net { fn drop(&mut self) { self.vu_common.shutdown(); if let Some(thread) = self.ctrl_queue_epoll_thread.take() && let Err(e) = thread.join() { error!("Error joining thread: {e:?}"); } } } impl VirtioDevice for Net { fn device_type(&self) -> u32 { self.vu_common.virtio_common.device_type } fn queue_max_sizes(&self) -> &[u16] { &self.vu_common.virtio_common.queue_sizes } fn features(&self) -> u64 { let mut features = self.vu_common.virtio_common.avail_features; if self.iommu { features |= 1u64 << VIRTIO_F_ACCESS_PLATFORM; } features } fn ack_features(&mut self, value: u64) { self.vu_common.virtio_common.ack_features(value); } fn read_config(&self, offset: u64, data: &mut [u8]) { self.read_config_from_slice(self.config.as_slice(), offset, data); } fn activate(&mut self, context: crate::device::ActivationContext) -> ActivateResult { let crate::device::ActivationContext { mem, interrupt_cb, mut queues, .. } = context; self.vu_common .virtio_common .activate(&queues, interrupt_cb.clone())?; self.guest_memory = Some(mem.clone()); let num_queues = queues.len(); let event_idx = self .vu_common .virtio_common .feature_acked(VIRTIO_RING_F_EVENT_IDX.into()); if self .vu_common .virtio_common .feature_acked(VIRTIO_NET_F_CTRL_VQ.into()) && !num_queues.is_multiple_of(2) { let ctrl_queue_index = num_queues - 1; let (_, mut ctrl_queue, ctrl_queue_evt) = queues.remove(ctrl_queue_index); ctrl_queue.set_event_idx(event_idx); let (kill_evt, pause_evt) = self.vu_common.virtio_common.dup_eventfds(); let mut ctrl_handler = NetCtrlEpollHandler { mem: mem.clone(), kill_evt, pause_evt, ctrl_q: CtrlQueue::new(Vec::new()), queue: ctrl_queue, queue_evt: ctrl_queue_evt, access_platform: None, interrupt_cb: interrupt_cb.clone(), queue_index: ctrl_queue_index as u16, }; let paused = self.vu_common.virtio_common.paused.clone(); // Let's update the barrier as we need 1 for the control queue // thread + 1 for the common vhost-user thread + 1 for the main // thread signalling the pause. self.vu_common.virtio_common.paused_sync = Some(Arc::new(Barrier::new(3))); let paused_sync = self.vu_common.virtio_common.paused_sync.clone(); let mut epoll_threads = Vec::new(); spawn_virtio_thread( &format!("{}_ctrl", &self.id), &self.seccomp_action, Thread::VirtioVhostNetCtl, &mut epoll_threads, &self.exit_evt, move || ctrl_handler.run_ctrl(&paused, paused_sync.as_ref().unwrap()), )?; self.ctrl_queue_epoll_thread = Some(epoll_threads.remove(0)); } 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); // Run a dedicated thread for handling potential reconnections with // the backend. let (kill_evt, pause_evt) = self.vu_common.virtio_common.dup_eventfds(); let mut handler = self.vu_common.activate( mem, &queues, interrupt_cb, backend_acked_features, backend_req_handler, kill_evt, pause_evt, )?; let paused = self.vu_common.virtio_common.paused.clone(); let paused_sync = self.vu_common.virtio_common.paused_sync.clone(); let mut epoll_threads = Vec::new(); spawn_virtio_thread( &self.id, &self.seccomp_action, Thread::VirtioVhostNet, &mut epoll_threads, &self.exit_evt, move || handler.run(&paused, paused_sync.as_ref().unwrap()), )?; self.vu_common.epoll_thread = Some(epoll_threads.remove(0)); Ok(()) } fn reset(&mut self) -> Option> { // We first must resume the virtio thread if it was paused. if self.vu_common.virtio_common.pause_evt.take().is_some() { self.vu_common.virtio_common.resume().ok()?; } if let Some(vu) = &self.vu_common.vu && let Err(e) = vu.lock().unwrap().reset_vhost_user() { error!("Failed to reset vhost-user daemon: {e:?}"); return None; } if let Some(kill_evt) = self.vu_common.virtio_common.kill_evt.take() { // Ignore the result because there is nothing we can do about it. let _ = kill_evt.write(1); } event!("virtio-device", "reset", "id", &self.id); // Return the interrupt Some(self.vu_common.virtio_common.interrupt_cb.take().unwrap()) } fn shutdown(&mut self) { self.vu_common.shutdown(); } fn add_memory_region( &mut self, region: &Arc, ) -> std::result::Result<(), crate::Error> { self.vu_common.add_memory_region(&self.guest_memory, region) } } impl Pausable for Net { fn pause(&mut self) -> result::Result<(), MigratableError> { self.vu_common.pause()?; self.vu_common.virtio_common.pause() } fn resume(&mut self) -> result::Result<(), MigratableError> { self.vu_common.virtio_common.resume()?; if let Some(epoll_thread) = &self.vu_common.epoll_thread { epoll_thread.thread().unpark(); } if let Some(ctrl_queue_epoll_thread) = &self.ctrl_queue_epoll_thread { ctrl_queue_epoll_thread.thread().unpark(); } self.vu_common.resume() } } impl Snapshottable for Net { fn id(&self) -> String { self.id.clone() } fn snapshot(&mut self) -> std::result::Result { self.vu_common.snapshot(&self.state()?) } } impl Transportable for Net {} impl Migratable for Net { fn start_dirty_log(&mut self) -> std::result::Result<(), MigratableError> { self.vu_common.start_dirty_log(&self.guest_memory) } fn stop_dirty_log(&mut self) -> std::result::Result<(), MigratableError> { self.vu_common.stop_dirty_log() } fn dirty_log(&mut self) -> std::result::Result { self.vu_common.dirty_log(&self.guest_memory) } fn start_migration(&mut self) -> std::result::Result<(), MigratableError> { self.vu_common.start_migration() } fn complete_migration(&mut self) -> std::result::Result<(), MigratableError> { self.vu_common.complete_migration() } }