mod sys;
use std::collections::BTreeMap;
use std::fmt;
use std::io;
use std::io::Write;
use std::net::Ipv4Addr;
use std::os::raw::c_uint;
use std::path::PathBuf;
use std::str::FromStr;
use anyhow::anyhow;
use anyhow::Context;
use base::error;
#[cfg(windows)]
use base::named_pipes::OverlappedWrapper;
use base::warn;
use base::Error as SysError;
use base::Event;
use base::EventToken;
use base::RawDescriptor;
use base::ReadNotifier;
use base::WaitContext;
use base::WorkerThread;
use data_model::Le16;
use data_model::Le64;
use net_util::Error as TapError;
use net_util::MacAddress;
use net_util::TapT;
use remain::sorted;
use serde::Deserialize;
use serde::Serialize;
use thiserror::Error as ThisError;
use virtio_sys::virtio_config::VIRTIO_F_RING_PACKED;
use virtio_sys::virtio_net;
use virtio_sys::virtio_net::VIRTIO_NET_CTRL_GUEST_OFFLOADS;
use virtio_sys::virtio_net::VIRTIO_NET_CTRL_GUEST_OFFLOADS_SET;
use virtio_sys::virtio_net::VIRTIO_NET_CTRL_MQ;
use virtio_sys::virtio_net::VIRTIO_NET_CTRL_MQ_VQ_PAIRS_SET;
use virtio_sys::virtio_net::VIRTIO_NET_ERR;
use virtio_sys::virtio_net::VIRTIO_NET_OK;
use vm_memory::GuestMemory;
use zerocopy::AsBytes;
use zerocopy::FromBytes;
use zerocopy::FromZeroes;
use super::copy_config;
use super::DeviceType;
use super::Interrupt;
use super::Queue;
use super::Reader;
use super::VirtioDevice;
use crate::PciAddress;
#[cfg(windows)]
pub(crate) const MAX_BUFFER_SIZE: usize = 65562;
const QUEUE_SIZE: u16 = 256;
#[cfg(any(target_os = "android", target_os = "linux"))]
pub static VHOST_NET_DEFAULT_PATH: &str = "/dev/vhost-net";
pub(crate) use sys::process_rx;
pub(crate) use sys::process_tx;
pub(crate) use sys::validate_and_configure_tap;
pub(crate) use sys::virtio_features_to_tap_offload;
#[sorted]
#[derive(ThisError, Debug)]
pub enum NetError {
#[error("failed to clone kill event: {0}")]
CloneKillEvent(SysError),
#[error("failed to create kill event: {0}")]
CreateKillEvent(SysError),
#[error("failed to create wait context: {0}")]
CreateWaitContext(SysError),
#[error("failed to add tap trigger to event context: {0}")]
EventAddTap(SysError),
#[error("failed to remove tap trigger from event context: {0}")]
EventRemoveTap(SysError),
#[error("invalid control command")]
InvalidCmd,
#[error("failed to read control message data: {0}")]
ReadCtrlData(io::Error),
#[error("failed to read control message header: {0}")]
ReadCtrlHeader(io::Error),
#[cfg(any(target_os = "android", target_os = "linux"))]
#[error("no rx descriptors available")]
RxDescriptorsExhausted,
#[cfg(windows)]
#[error("error creating Slirp: {0}")]
SlirpCreateError(net_util::Error),
#[error("failed to enable tap interface: {0}")]
TapEnable(TapError),
#[error("failed to get tap interface MTU: {0}")]
TapGetMtu(TapError),
#[error("failed to open tap device: {0}")]
TapOpen(TapError),
#[error("failed to set tap IP: {0}")]
TapSetIp(TapError),
#[error("failed to set tap mac address: {0}")]
TapSetMacAddress(TapError),
#[error("failed to set tap netmask: {0}")]
TapSetNetmask(TapError),
#[error("failed to set tap offload: {0}")]
TapSetOffload(TapError),
#[error("failed to set vnet header size: {0}")]
TapSetVnetHdrSize(TapError),
#[error("failed to validate tap interface: {0}")]
TapValidate(String),
#[error("failed to disable EPOLLIN on tap fd: {0}")]
WaitContextDisableTap(SysError),
#[error("failed to enable EPOLLIN on tap fd: {0}")]
WaitContextEnableTap(SysError),
#[error("error while waiting for events: {0}")]
WaitError(SysError),
#[error("failed to write control message ack: {0}")]
WriteAck(io::Error),
#[cfg(any(target_os = "android", target_os = "linux"))]
#[error("failed to write to guest buffer: {0}")]
WriteBuffer(io::Error),
}
#[derive(Serialize, Deserialize, PartialEq, Eq, Debug)]
#[serde(untagged, deny_unknown_fields)]
pub enum NetParametersMode {
#[serde(rename_all = "kebab-case")]
TapName {
tap_name: String,
mac: Option<MacAddress>,
},
#[serde(rename_all = "kebab-case")]
TapFd {
tap_fd: i32,
mac: Option<MacAddress>,
},
#[serde(rename_all = "kebab-case")]
RawConfig {
host_ip: Ipv4Addr,
netmask: Ipv4Addr,
mac: MacAddress,
},
}
#[cfg(any(target_os = "android", target_os = "linux"))]
fn vhost_net_device_path_default() -> PathBuf {
PathBuf::from(VHOST_NET_DEFAULT_PATH)
}
#[cfg(any(target_os = "android", target_os = "linux"))]
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
pub struct VhostNetParameters {
#[serde(default = "vhost_net_device_path_default")]
pub device: PathBuf,
}
#[cfg(any(target_os = "android", target_os = "linux"))]
impl Default for VhostNetParameters {
fn default() -> Self {
Self {
device: vhost_net_device_path_default(),
}
}
}
#[derive(Serialize, Deserialize, PartialEq, Eq, Debug)]
#[serde(rename_all = "kebab-case")]
pub struct NetParameters {
#[serde(flatten)]
pub mode: NetParametersMode,
pub vq_pairs: Option<u16>,
#[cfg(any(target_os = "android", target_os = "linux"))]
pub vhost_net: Option<VhostNetParameters>,
#[serde(default)]
pub packed_queue: bool,
pub pci_address: Option<PciAddress>,
}
impl FromStr for NetParameters {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
serde_keyvalue::from_key_values(s).map_err(|e| e.to_string())
}
}
#[repr(C, packed)]
#[derive(Debug, Clone, Copy, AsBytes, FromZeroes, FromBytes)]
pub struct virtio_net_ctrl_hdr {
pub class: u8,
pub cmd: u8,
}
#[derive(Debug, Clone, Copy, Default, AsBytes, FromZeroes, FromBytes)]
#[repr(C)]
pub struct VirtioNetConfig {
mac: [u8; 6],
status: Le16,
max_vq_pairs: Le16,
mtu: Le16,
}
fn process_ctrl_request<T: TapT>(
reader: &mut Reader,
tap: &mut T,
acked_features: u64,
vq_pairs: u16,
) -> Result<(), NetError> {
let ctrl_hdr: virtio_net_ctrl_hdr = reader.read_obj().map_err(NetError::ReadCtrlHeader)?;
match ctrl_hdr.class as c_uint {
VIRTIO_NET_CTRL_GUEST_OFFLOADS => {
if ctrl_hdr.cmd != VIRTIO_NET_CTRL_GUEST_OFFLOADS_SET as u8 {
error!(
"invalid cmd for VIRTIO_NET_CTRL_GUEST_OFFLOADS: {}",
ctrl_hdr.cmd
);
return Err(NetError::InvalidCmd);
}
let offloads: Le64 = reader.read_obj().map_err(NetError::ReadCtrlData)?;
let tap_offloads = virtio_features_to_tap_offload(offloads.into());
tap.set_offload(tap_offloads)
.map_err(NetError::TapSetOffload)?;
}
VIRTIO_NET_CTRL_MQ => {
if ctrl_hdr.cmd == VIRTIO_NET_CTRL_MQ_VQ_PAIRS_SET as u8 {
let pairs: Le16 = reader.read_obj().map_err(NetError::ReadCtrlData)?;
if acked_features & 1 << virtio_net::VIRTIO_NET_F_MQ == 0
|| pairs.to_native() != vq_pairs
{
error!(
"Invalid VQ_PAIRS_SET cmd, driver request pairs: {}, device vq pairs: {}",
pairs.to_native(),
vq_pairs
);
return Err(NetError::InvalidCmd);
}
}
}
_ => {
warn!(
"unimplemented class for VIRTIO_NET_CTRL_GUEST_OFFLOADS: {}",
ctrl_hdr.class
);
return Err(NetError::InvalidCmd);
}
}
Ok(())
}
pub fn process_ctrl<T: TapT>(
ctrl_queue: &mut Queue,
tap: &mut T,
acked_features: u64,
vq_pairs: u16,
) -> Result<(), NetError> {
while let Some(mut desc_chain) = ctrl_queue.pop() {
if let Err(e) = process_ctrl_request(&mut desc_chain.reader, tap, acked_features, vq_pairs)
{
error!("process_ctrl_request failed: {}", e);
desc_chain
.writer
.write_all(&[VIRTIO_NET_ERR as u8])
.map_err(NetError::WriteAck)?;
} else {
desc_chain
.writer
.write_all(&[VIRTIO_NET_OK as u8])
.map_err(NetError::WriteAck)?;
}
let len = desc_chain.writer.bytes_written() as u32;
ctrl_queue.add_used(desc_chain, len);
}
ctrl_queue.trigger_interrupt();
Ok(())
}
#[derive(EventToken, Debug, Clone)]
pub enum Token {
RxTap,
RxQueue,
TxQueue,
CtrlQueue,
InterruptResample,
Kill,
}
pub(super) struct Worker<T: TapT> {
pub(super) interrupt: Interrupt,
pub(super) rx_queue: Queue,
pub(super) tx_queue: Queue,
pub(super) ctrl_queue: Option<Queue>,
pub(super) tap: T,
#[cfg(windows)]
pub(super) overlapped_wrapper: OverlappedWrapper,
#[cfg(windows)]
pub(super) rx_buf: [u8; MAX_BUFFER_SIZE],
#[cfg(windows)]
pub(super) rx_count: usize,
#[cfg(windows)]
pub(super) deferred_rx: bool,
acked_features: u64,
vq_pairs: u16,
#[allow(dead_code)]
kill_evt: Event,
}
impl<T> Worker<T>
where
T: TapT + ReadNotifier,
{
fn process_tx(&mut self) {
process_tx(&mut self.tx_queue, &mut self.tap)
}
fn process_ctrl(&mut self) -> Result<(), NetError> {
let ctrl_queue = match self.ctrl_queue.as_mut() {
Some(queue) => queue,
None => return Ok(()),
};
process_ctrl(
ctrl_queue,
&mut self.tap,
self.acked_features,
self.vq_pairs,
)
}
fn run(&mut self, handle_interrupt_resample: bool) -> Result<(), NetError> {
let wait_ctx: WaitContext<Token> = WaitContext::build_with(&[
#[cfg(windows)]
(
self.overlapped_wrapper.get_h_event_ref().unwrap(),
Token::RxTap,
),
#[cfg(any(target_os = "android", target_os = "linux"))]
(self.tap.get_read_notifier(), Token::RxTap),
(self.rx_queue.event(), Token::RxQueue),
(self.tx_queue.event(), Token::TxQueue),
(&self.kill_evt, Token::Kill),
])
.map_err(NetError::CreateWaitContext)?;
if let Some(ctrl_queue) = &self.ctrl_queue {
wait_ctx
.add(ctrl_queue.event(), Token::CtrlQueue)
.map_err(NetError::CreateWaitContext)?;
}
if handle_interrupt_resample {
if let Some(resample_evt) = self.interrupt.get_resample_evt() {
wait_ctx
.add(resample_evt, Token::InterruptResample)
.map_err(NetError::CreateWaitContext)?;
}
}
let mut tap_polling_enabled = true;
'wait: loop {
let events = wait_ctx.wait().map_err(NetError::WaitError)?;
for event in events.iter().filter(|e| e.is_readable) {
match event.token {
Token::RxTap => {
let _trace = cros_tracing::trace_event!(VirtioNet, "handle RxTap event");
self.handle_rx_token(&wait_ctx)?;
tap_polling_enabled = false;
}
Token::RxQueue => {
let _trace = cros_tracing::trace_event!(VirtioNet, "handle RxQueue event");
if let Err(e) = self.rx_queue.event().wait() {
error!("net: error reading rx queue Event: {}", e);
break 'wait;
}
self.handle_rx_queue(&wait_ctx, tap_polling_enabled)?;
tap_polling_enabled = true;
}
Token::TxQueue => {
let _trace = cros_tracing::trace_event!(VirtioNet, "handle TxQueue event");
if let Err(e) = self.tx_queue.event().wait() {
error!("net: error reading tx queue Event: {}", e);
break 'wait;
}
self.process_tx();
}
Token::CtrlQueue => {
let _trace =
cros_tracing::trace_event!(VirtioNet, "handle CtrlQueue event");
if let Some(ctrl_evt) = self.ctrl_queue.as_ref().map(|q| q.event()) {
if let Err(e) = ctrl_evt.wait() {
error!("net: error reading ctrl queue Event: {}", e);
break 'wait;
}
} else {
break 'wait;
}
if let Err(e) = self.process_ctrl() {
error!("net: failed to process control message: {}", e);
break 'wait;
}
}
Token::InterruptResample => {
let _trace =
cros_tracing::trace_event!(VirtioNet, "handle InterruptResample event");
let _ = self.interrupt.get_resample_evt().unwrap().wait();
self.interrupt.do_interrupt_resample();
}
Token::Kill => {
let _ = self.kill_evt.wait();
break 'wait;
}
}
}
}
Ok(())
}
}
pub fn build_config(vq_pairs: u16, mtu: u16, mac: Option<[u8; 6]>) -> VirtioNetConfig {
VirtioNetConfig {
max_vq_pairs: Le16::from(vq_pairs),
mtu: Le16::from(mtu),
mac: mac.unwrap_or_default(),
..Default::default()
}
}
pub struct Net<T: TapT + ReadNotifier + 'static> {
guest_mac: Option<[u8; 6]>,
queue_sizes: Box<[u16]>,
worker_threads: Vec<WorkerThread<Worker<T>>>,
taps: Vec<T>,
avail_features: u64,
acked_features: u64,
mtu: u16,
pci_address: Option<PciAddress>,
#[cfg(windows)]
slirp_kill_evt: Option<Event>,
}
#[derive(Serialize, Deserialize)]
struct NetSnapshot {
avail_features: u64,
acked_features: u64,
}
impl<T> Net<T>
where
T: TapT + ReadNotifier,
{
pub fn new(
base_features: u64,
tap: T,
vq_pairs: u16,
mac_addr: Option<MacAddress>,
use_packed_queue: bool,
pci_address: Option<PciAddress>,
) -> Result<Net<T>, NetError> {
let taps = tap.into_mq_taps(vq_pairs).map_err(NetError::TapOpen)?;
let mut mtu = u16::MAX;
for tap in &taps {
validate_and_configure_tap(tap, vq_pairs)?;
mtu = std::cmp::min(mtu, tap.mtu().map_err(NetError::TapGetMtu)?);
}
let mut avail_features = base_features
| 1 << virtio_net::VIRTIO_NET_F_GUEST_CSUM
| 1 << virtio_net::VIRTIO_NET_F_CSUM
| 1 << virtio_net::VIRTIO_NET_F_CTRL_VQ
| 1 << virtio_net::VIRTIO_NET_F_CTRL_GUEST_OFFLOADS
| 1 << virtio_net::VIRTIO_NET_F_GUEST_TSO4
| 1 << virtio_net::VIRTIO_NET_F_GUEST_UFO
| 1 << virtio_net::VIRTIO_NET_F_HOST_TSO4
| 1 << virtio_net::VIRTIO_NET_F_HOST_UFO
| 1 << virtio_net::VIRTIO_NET_F_MTU;
if vq_pairs > 1 {
avail_features |= 1 << virtio_net::VIRTIO_NET_F_MQ;
}
if use_packed_queue {
avail_features |= 1 << VIRTIO_F_RING_PACKED;
}
if mac_addr.is_some() {
avail_features |= 1 << virtio_net::VIRTIO_NET_F_MAC;
}
Self::new_internal(
taps,
avail_features,
mtu,
mac_addr,
pci_address,
#[cfg(windows)]
None,
)
}
pub(crate) fn new_internal(
taps: Vec<T>,
avail_features: u64,
mtu: u16,
mac_addr: Option<MacAddress>,
pci_address: Option<PciAddress>,
#[cfg(windows)] slirp_kill_evt: Option<Event>,
) -> Result<Self, NetError> {
let net = Self {
guest_mac: mac_addr.map(|mac| mac.octets()),
queue_sizes: vec![QUEUE_SIZE; taps.len() * 2 + 1].into_boxed_slice(),
worker_threads: Vec::new(),
taps,
avail_features,
acked_features: 0u64,
mtu,
pci_address,
#[cfg(windows)]
slirp_kill_evt: None,
};
cros_tracing::trace_simple_print!("New Net device created: {:?}", net);
Ok(net)
}
fn max_virtqueue_pairs(&self) -> usize {
self.taps.len()
}
}
impl<T> Drop for Net<T>
where
T: TapT + ReadNotifier,
{
fn drop(&mut self) {
#[cfg(windows)]
{
if let Some(slirp_kill_evt) = self.slirp_kill_evt.take() {
let _ = slirp_kill_evt.signal();
}
}
}
}
impl<T> VirtioDevice for Net<T>
where
T: 'static + TapT + ReadNotifier,
{
fn keep_rds(&self) -> Vec<RawDescriptor> {
let mut keep_rds = Vec::new();
for tap in &self.taps {
keep_rds.push(tap.as_raw_descriptor());
}
keep_rds
}
fn device_type(&self) -> DeviceType {
DeviceType::Net
}
fn queue_max_sizes(&self) -> &[u16] {
&self.queue_sizes
}
fn features(&self) -> u64 {
self.avail_features
}
fn ack_features(&mut self, value: u64) {
let mut v = value;
let unrequested_features = v & !self.avail_features;
if unrequested_features != 0 {
warn!("net: virtio net got unknown feature ack: {:x}", v);
v &= !unrequested_features;
}
self.acked_features |= v;
if let Some(tap) = self.taps.first() {
if let Err(e) = tap.set_offload(virtio_features_to_tap_offload(self.acked_features)) {
warn!(
"net: failed to set tap offload to match acked features: {}",
e
);
}
}
}
fn read_config(&self, offset: u64, data: &mut [u8]) {
let vq_pairs = self.queue_sizes.len() / 2;
let config_space = build_config(vq_pairs as u16, self.mtu, self.guest_mac);
copy_config(data, 0, config_space.as_bytes(), offset);
}
fn activate(
&mut self,
_mem: GuestMemory,
interrupt: Interrupt,
mut queues: BTreeMap<usize, Queue>,
) -> anyhow::Result<()> {
let ctrl_vq_enabled = self.acked_features & (1 << virtio_net::VIRTIO_NET_F_CTRL_VQ) != 0;
let mq_enabled = self.acked_features & (1 << virtio_net::VIRTIO_NET_F_MQ) != 0;
let vq_pairs = if mq_enabled {
self.max_virtqueue_pairs()
} else {
1
};
let mut num_queues_expected = vq_pairs * 2;
if ctrl_vq_enabled {
num_queues_expected += 1;
}
if queues.len() != num_queues_expected {
return Err(anyhow!(
"net: expected {} queues, got {} queues",
self.queue_sizes.len(),
queues.len(),
));
}
if self.taps.len() < vq_pairs {
return Err(anyhow!(
"net: expected {} taps, got {}",
vq_pairs,
self.taps.len()
));
}
for i in 0..vq_pairs {
let tap = self.taps.remove(0);
let acked_features = self.acked_features;
let interrupt = interrupt.clone();
let first_queue = i == 0;
let rx_queue = queues.pop_first().unwrap().1;
let tx_queue = queues.pop_first().unwrap().1;
let ctrl_queue = if first_queue && ctrl_vq_enabled {
Some(queues.pop_last().unwrap().1)
} else {
None
};
let handle_interrupt_resample = first_queue;
let pairs = vq_pairs as u16;
#[cfg(windows)]
let overlapped_wrapper = OverlappedWrapper::new(true).unwrap();
self.worker_threads
.push(WorkerThread::start(format!("v_net:{i}"), move |kill_evt| {
let mut worker = Worker {
interrupt,
rx_queue,
tx_queue,
ctrl_queue,
tap,
#[cfg(windows)]
overlapped_wrapper,
acked_features,
vq_pairs: pairs,
#[cfg(windows)]
rx_buf: [0u8; MAX_BUFFER_SIZE],
#[cfg(windows)]
rx_count: 0,
#[cfg(windows)]
deferred_rx: false,
kill_evt,
};
let result = worker.run(handle_interrupt_resample);
if let Err(e) = result {
error!("net worker thread exited with error: {}", e);
}
worker
}));
}
cros_tracing::trace_simple_print!("Net device activated: {:?}", self);
Ok(())
}
fn pci_address(&self) -> Option<PciAddress> {
self.pci_address
}
fn virtio_sleep(&mut self) -> anyhow::Result<Option<BTreeMap<usize, Queue>>> {
if self.worker_threads.is_empty() {
return Ok(None);
}
let mut queues = BTreeMap::new();
let mut queue_index = 0;
let mut ctrl_queue = None;
for worker_thread in self.worker_threads.drain(..) {
let mut worker = worker_thread.stop();
if worker.ctrl_queue.is_some() {
ctrl_queue = worker.ctrl_queue.take();
}
self.taps.push(worker.tap);
queues.insert(queue_index + 0, worker.rx_queue);
queues.insert(queue_index + 1, worker.tx_queue);
queue_index += 2;
}
if let Some(ctrl_queue) = ctrl_queue {
queues.insert(queue_index, ctrl_queue);
}
Ok(Some(queues))
}
fn virtio_wake(
&mut self,
device_state: Option<(GuestMemory, Interrupt, BTreeMap<usize, Queue>)>,
) -> anyhow::Result<()> {
match device_state {
None => Ok(()),
Some((mem, interrupt, queues)) => {
self.activate(mem, interrupt, queues)?;
Ok(())
}
}
}
fn virtio_snapshot(&mut self) -> anyhow::Result<serde_json::Value> {
serde_json::to_value(NetSnapshot {
acked_features: self.acked_features,
avail_features: self.avail_features,
})
.context("failed to snapshot virtio Net device")
}
fn virtio_restore(&mut self, data: serde_json::Value) -> anyhow::Result<()> {
let deser: NetSnapshot =
serde_json::from_value(data).context("failed to deserialize Net device")?;
anyhow::ensure!(
self.avail_features == deser.avail_features,
"Available features for net device do not match. expected: {}, got: {}",
deser.avail_features,
self.avail_features
);
self.acked_features = deser.acked_features;
Ok(())
}
fn reset(&mut self) -> anyhow::Result<()> {
for worker_thread in self.worker_threads.drain(..) {
let worker = worker_thread.stop();
self.taps.push(worker.tap);
}
Ok(())
}
}
impl<T> std::fmt::Debug for Net<T>
where
T: TapT + ReadNotifier,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Net")
.field("guest_mac", &self.guest_mac)
.field("queue_sizes", &self.queue_sizes)
.field("worker_threads_size", &self.worker_threads.len())
.field("taps_size", &self.taps.len())
.field("avail_features", &self.avail_features)
.field("acked_features", &self.acked_features)
.field("mtu", &self.mtu)
.finish()
}
}
#[cfg(test)]
mod tests {
use serde_keyvalue::*;
use super::*;
fn from_net_arg(options: &str) -> Result<NetParameters, ParseError> {
from_key_values(options)
}
#[test]
fn params_from_key_values() {
let params = from_net_arg("");
assert!(params.is_err());
let params = from_net_arg("tap-name=tap").unwrap();
assert_eq!(
params,
NetParameters {
#[cfg(any(target_os = "android", target_os = "linux"))]
vhost_net: None,
vq_pairs: None,
mode: NetParametersMode::TapName {
tap_name: "tap".to_string(),
mac: None
},
packed_queue: false,
pci_address: None,
}
);
let params = from_net_arg("tap-name=tap,mac=\"3d:70:eb:61:1a:91\"").unwrap();
assert_eq!(
params,
NetParameters {
#[cfg(any(target_os = "android", target_os = "linux"))]
vhost_net: None,
vq_pairs: None,
mode: NetParametersMode::TapName {
tap_name: "tap".to_string(),
mac: Some(MacAddress::from_str("3d:70:eb:61:1a:91").unwrap())
},
packed_queue: false,
pci_address: None,
}
);
let params = from_net_arg("tap-fd=12").unwrap();
assert_eq!(
params,
NetParameters {
#[cfg(any(target_os = "android", target_os = "linux"))]
vhost_net: None,
vq_pairs: None,
mode: NetParametersMode::TapFd {
tap_fd: 12,
mac: None
},
packed_queue: false,
pci_address: None,
}
);
let params = from_net_arg("tap-fd=12,mac=\"3d:70:eb:61:1a:91\"").unwrap();
assert_eq!(
params,
NetParameters {
#[cfg(any(target_os = "android", target_os = "linux"))]
vhost_net: None,
vq_pairs: None,
mode: NetParametersMode::TapFd {
tap_fd: 12,
mac: Some(MacAddress::from_str("3d:70:eb:61:1a:91").unwrap())
},
packed_queue: false,
pci_address: None,
}
);
let params = from_net_arg(
"host-ip=\"192.168.10.1\",netmask=\"255.255.255.0\",mac=\"3d:70:eb:61:1a:91\"",
)
.unwrap();
assert_eq!(
params,
NetParameters {
#[cfg(any(target_os = "android", target_os = "linux"))]
vhost_net: None,
vq_pairs: None,
mode: NetParametersMode::RawConfig {
host_ip: Ipv4Addr::from_str("192.168.10.1").unwrap(),
netmask: Ipv4Addr::from_str("255.255.255.0").unwrap(),
mac: MacAddress::from_str("3d:70:eb:61:1a:91").unwrap(),
},
packed_queue: false,
pci_address: None,
}
);
let params = from_net_arg("tap-fd=12,pci-address=00:01.1").unwrap();
assert_eq!(
params,
NetParameters {
#[cfg(any(target_os = "android", target_os = "linux"))]
vhost_net: None,
vq_pairs: None,
mode: NetParametersMode::TapFd {
tap_fd: 12,
mac: None,
},
packed_queue: false,
pci_address: Some(PciAddress {
bus: 0,
dev: 1,
func: 1,
}),
}
);
assert!(from_net_arg("tap-fd=12,pci-address=hello").is_err());
assert!(from_net_arg("host-ip=\"192.168.10.1\",mac=\"3d:70:eb:61:1a:91\"").is_err());
assert!(from_net_arg("tap-name=tap,foomatic=true").is_err());
}
#[test]
#[cfg(any(target_os = "android", target_os = "linux"))]
fn params_from_key_values_vhost_net() {
let params = from_net_arg(
"vhost-net=[device=/dev/foo],\
host-ip=\"192.168.10.1\",\
netmask=\"255.255.255.0\",\
mac=\"3d:70:eb:61:1a:91\"",
)
.unwrap();
assert_eq!(
params,
NetParameters {
vhost_net: Some(VhostNetParameters {
device: PathBuf::from("/dev/foo")
}),
vq_pairs: None,
mode: NetParametersMode::RawConfig {
host_ip: Ipv4Addr::from_str("192.168.10.1").unwrap(),
netmask: Ipv4Addr::from_str("255.255.255.0").unwrap(),
mac: MacAddress::from_str("3d:70:eb:61:1a:91").unwrap(),
},
packed_queue: false,
pci_address: None,
}
);
let params = from_net_arg("tap-fd=3,vhost-net").unwrap();
assert_eq!(
params,
NetParameters {
vhost_net: Some(Default::default()),
vq_pairs: None,
mode: NetParametersMode::TapFd {
tap_fd: 3,
mac: None
},
packed_queue: false,
pci_address: None,
}
);
let params = from_net_arg("vhost-net,tap-name=crosvm_tap").unwrap();
assert_eq!(
params,
NetParameters {
vhost_net: Some(Default::default()),
vq_pairs: None,
mode: NetParametersMode::TapName {
tap_name: "crosvm_tap".to_owned(),
mac: None
},
packed_queue: false,
pci_address: None,
}
);
let params =
from_net_arg("vhost-net,mac=\"3d:70:eb:61:1a:91\",tap-name=crosvm_tap").unwrap();
assert_eq!(
params,
NetParameters {
vhost_net: Some(Default::default()),
vq_pairs: None,
mode: NetParametersMode::TapName {
tap_name: "crosvm_tap".to_owned(),
mac: Some(MacAddress::from_str("3d:70:eb:61:1a:91").unwrap())
},
packed_queue: false,
pci_address: None,
}
);
let params = from_net_arg("tap-name=tap,packed-queue=true").unwrap();
assert_eq!(
params,
NetParameters {
#[cfg(any(target_os = "android", target_os = "linux"))]
vhost_net: None,
vq_pairs: None,
mode: NetParametersMode::TapName {
tap_name: "tap".to_string(),
mac: None
},
packed_queue: true,
pci_address: None,
}
);
let params = from_net_arg("tap-name=tap,packed-queue").unwrap();
assert_eq!(
params,
NetParameters {
#[cfg(any(target_os = "android", target_os = "linux"))]
vhost_net: None,
vq_pairs: None,
mode: NetParametersMode::TapName {
tap_name: "tap".to_string(),
mac: None
},
packed_queue: true,
pci_address: None,
}
);
let params = from_net_arg("vhost-net,tap-name=crosvm_tap,pci-address=00:01.1").unwrap();
assert_eq!(
params,
NetParameters {
vhost_net: Some(Default::default()),
vq_pairs: None,
mode: NetParametersMode::TapName {
tap_name: "crosvm_tap".to_owned(),
mac: None,
},
packed_queue: false,
pci_address: Some(PciAddress {
bus: 0,
dev: 1,
func: 1,
}),
}
);
assert!(from_net_arg(
"tap-name=tap,\
vhost-net,\
host-ip=\"192.168.10.1\",\
netmask=\"255.255.255.0\",\
mac=\"3d:70:eb:61:1a:91\"",
)
.is_err());
}
}