diff --git a/vm-virtio/src/net.rs b/vm-virtio/src/net.rs index 050788bca..3c7fd1613 100644 --- a/vm-virtio/src/net.rs +++ b/vm-virtio/src/net.rs @@ -15,6 +15,7 @@ use super::{ ActivateError, ActivateResult, Queue, VirtioDevice, VirtioDeviceType, VirtioInterruptType, }; use crate::VirtioInterrupt; +use anyhow::anyhow; use epoll; use libc::EAGAIN; use libc::EFD_NONBLOCK; @@ -31,7 +32,10 @@ use std::thread; use std::vec::Vec; use virtio_bindings::bindings::virtio_net::*; use vm_memory::{ByteValued, GuestAddressSpace, GuestMemoryAtomic, GuestMemoryMmap}; -use vm_migration::{Migratable, MigratableError, Pausable, Snapshottable, Transportable}; +use vm_migration::{ + Migratable, MigratableError, Pausable, Snapshot, SnapshotDataSection, Snapshottable, + Transportable, +}; use vmm_sys_util::eventfd::EventFd; #[derive(Debug)] @@ -234,6 +238,19 @@ impl NetEpollHandler { ) .map_err(DeviceError::EpollCtl)?; + // If there are some already available descriptors on the RX queue, + // then we can start the thread while listening onto the TAP. + if queues[0].available_descriptors(&self.mem.memory()).unwrap() { + epoll::ctl( + self.epoll_fd, + epoll::ControlOptions::EPOLL_CTL_ADD, + self.tap.as_raw_fd(), + epoll::Event::new(epoll::Events::EPOLLIN, u64::from(RX_TAP_EVENT)), + ) + .map_err(DeviceError::EpollCtl)?; + self.rx_tap_listening = true; + } + let mut events = vec![epoll::Event::new(epoll::Events::empty(), 0); NET_EVENTS_COUNT]; 'epoll: loop { @@ -275,6 +292,7 @@ impl NetEpollHandler { // Drain pause event let _ = self.pause_evt.read(); debug!("PAUSE_EVENT received, pausing virtio-net epoll loop"); + // We loop here to handle spurious park() returns. // Until we have not resumed, the paused boolean will // be true. @@ -307,6 +325,14 @@ pub struct Net { queue_size: Vec, } +#[derive(Serialize, Deserialize)] +pub struct NetState { + pub avail_features: u64, + pub acked_features: u64, + pub config: VirtioNetConfig, + pub queue_size: Vec, +} + impl Net { /// Create a new virtio network device with the given TAP interface. pub fn new_with_tap( @@ -369,6 +395,24 @@ impl Net { Self::new_with_tap(taps, guest_mac, iommu, num_queues, queue_size) } + + fn state(&self) -> NetState { + NetState { + avail_features: self.avail_features, + acked_features: self.acked_features, + config: self.config, + queue_size: self.queue_size.clone(), + } + } + + fn set_state(&mut self, state: &NetState) -> Result<()> { + self.avail_features = state.avail_features; + self.acked_features = state.acked_features; + self.config = state.config; + self.queue_size = state.queue_size.clone(); + + Ok(()) + } } impl Drop for Net { @@ -567,6 +611,49 @@ impl VirtioDevice for Net { } virtio_ctrl_q_pausable!(Net); -impl Snapshottable for Net {} +const NET_SNAPSHOT_ID: &str = "virtio-net"; +impl Snapshottable for Net { + fn id(&self) -> String { + NET_SNAPSHOT_ID.to_string() + } + + fn snapshot(&self) -> std::result::Result { + let snapshot = + serde_json::to_vec(&self.state()).map_err(|e| MigratableError::Snapshot(e.into()))?; + + let mut net_snapshot = Snapshot::new(NET_SNAPSHOT_ID); + net_snapshot.add_data_section(SnapshotDataSection { + id: format!("{}-section", NET_SNAPSHOT_ID), + snapshot, + }); + + Ok(net_snapshot) + } + + fn restore(&mut self, snapshot: Snapshot) -> std::result::Result<(), MigratableError> { + if let Some(net_section) = snapshot + .snapshot_data + .get(&format!("{}-section", NET_SNAPSHOT_ID)) + { + let net_state = match serde_json::from_slice(&net_section.snapshot) { + Ok(state) => state, + Err(error) => { + return Err(MigratableError::Restore(anyhow!( + "Could not deserialize NET {}", + error + ))) + } + }; + + return self.set_state(&net_state).map_err(|e| { + MigratableError::Restore(anyhow!("Could not restore NET state {:?}", e)) + }); + } + + Err(MigratableError::Restore(anyhow!( + "Could not find NET snapshot section" + ))) + } +} impl Transportable for Net {} impl Migratable for Net {} diff --git a/vm-virtio/src/net_util.rs b/vm-virtio/src/net_util.rs index 00394b381..6f8aaf71a 100644 --- a/vm-virtio/src/net_util.rs +++ b/vm-virtio/src/net_util.rs @@ -5,6 +5,7 @@ use super::Error as DeviceError; use super::{DescriptorChain, DeviceEventT, Queue}; use net_util::{MacAddr, Tap, TapError}; +use serde::ser::{Serialize, SerializeStruct, Serializer}; use std::cmp; use std::fs; use std::io::{self, Write}; @@ -47,7 +48,7 @@ const CTRL_QUEUE_EVENT: DeviceEventT = 0; const CTRL_EVENT_COUNT: usize = 3; #[repr(C, packed)] -#[derive(Copy, Clone, Debug, Default)] +#[derive(Copy, Clone, Debug, Default, Deserialize)] pub struct VirtioNetConfig { pub mac: [u8; 6], pub status: u16, @@ -57,6 +58,34 @@ pub struct VirtioNetConfig { pub duplex: u8, } +// We must explicitly implement Serialize since the structure is packed and +// it's unsafe to borrow from a packed structure. And by default, if we derive +// Serialize from serde, it will borrow the values from the structure. +// That's why this implementation copies each field separately before it +// serializes the entire structure field by field. +impl Serialize for VirtioNetConfig { + fn serialize(&self, serializer: S) -> std::result::Result + where + S: Serializer, + { + let mac = self.mac; + let status = self.status; + let max_virtqueue_pairs = self.max_virtqueue_pairs; + let mtu = self.mtu; + let speed = self.speed; + let duplex = self.duplex; + + let mut virtio_net_config = serializer.serialize_struct("VirtioNetConfig", 17)?; + virtio_net_config.serialize_field("mac", &mac)?; + virtio_net_config.serialize_field("status", &status)?; + virtio_net_config.serialize_field("max_virtqueue_pairs", &max_virtqueue_pairs)?; + virtio_net_config.serialize_field("mtu", &mtu)?; + virtio_net_config.serialize_field("speed", &speed)?; + virtio_net_config.serialize_field("duplex", &duplex)?; + virtio_net_config.end() + } +} + // Safe because it only has data and has no implicit padding. unsafe impl ByteValued for VirtioNetConfig {}