blob: 542423ab5413f2bc65c0be1366575ddaba4c85aa [file] [log] [blame]
// Copyright 2017 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use std::mem;
use std::net::Ipv4Addr;
use std::os::unix::io::{AsRawFd, RawFd};
use std::thread;
use net_sys;
use net_util::{MacAddress, TapT};
use sys_util::{error, warn, EventFd, GuestMemory};
use vhost::NetT as VhostNetT;
use virtio_sys::virtio_net;
use super::control_socket::*;
use super::worker::Worker;
use super::{Error, Result};
use crate::pci::MsixStatus;
use crate::virtio::{Interrupt, Queue, VirtioDevice, TYPE_NET};
use msg_socket::{MsgReceiver, MsgSender};
const QUEUE_SIZE: u16 = 256;
const NUM_QUEUES: usize = 2;
const QUEUE_SIZES: &[u16] = &[QUEUE_SIZE; NUM_QUEUES];
pub struct Net<T: TapT, U: VhostNetT<T>> {
workers_kill_evt: Option<EventFd>,
kill_evt: EventFd,
worker_thread: Option<thread::JoinHandle<(Worker<U>, T)>>,
tap: Option<T>,
vhost_net_handle: Option<U>,
vhost_interrupt: Option<Vec<EventFd>>,
avail_features: u64,
acked_features: u64,
request_socket: Option<VhostDevRequestSocket>,
response_socket: Option<VhostDevResponseSocket>,
}
impl<T, U> Net<T, U>
where
T: TapT,
U: VhostNetT<T>,
{
/// Create a new virtio network device with the given IP address and
/// netmask.
pub fn new(
ip_addr: Ipv4Addr,
netmask: Ipv4Addr,
mac_addr: MacAddress,
mem: &GuestMemory,
) -> Result<Net<T, U>> {
let kill_evt = EventFd::new().map_err(Error::CreateKillEventFd)?;
let tap: T = T::new(true).map_err(Error::TapOpen)?;
tap.set_ip_addr(ip_addr).map_err(Error::TapSetIp)?;
tap.set_netmask(netmask).map_err(Error::TapSetNetmask)?;
tap.set_mac_address(mac_addr)
.map_err(Error::TapSetMacAddress)?;
// Set offload flags to match the virtio features below.
tap.set_offload(
net_sys::TUN_F_CSUM | net_sys::TUN_F_UFO | net_sys::TUN_F_TSO4 | net_sys::TUN_F_TSO6,
)
.map_err(Error::TapSetOffload)?;
// We declare VIRTIO_NET_F_MRG_RXBUF, so set the vnet hdr size to match.
let vnet_hdr_size = mem::size_of::<virtio_net::virtio_net_hdr_mrg_rxbuf>() as i32;
tap.set_vnet_hdr_size(vnet_hdr_size)
.map_err(Error::TapSetVnetHdrSize)?;
tap.enable().map_err(Error::TapEnable)?;
let vhost_net_handle = U::new(mem).map_err(Error::VhostOpen)?;
let avail_features = 1 << virtio_net::VIRTIO_NET_F_GUEST_CSUM
| 1 << virtio_net::VIRTIO_NET_F_CSUM
| 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_MRG_RXBUF
| 1 << virtio_sys::vhost::VIRTIO_RING_F_INDIRECT_DESC
| 1 << virtio_sys::vhost::VIRTIO_RING_F_EVENT_IDX
| 1 << virtio_sys::vhost::VIRTIO_F_NOTIFY_ON_EMPTY
| 1 << virtio_sys::vhost::VIRTIO_F_VERSION_1;
let mut vhost_interrupt = Vec::new();
for _ in 0..NUM_QUEUES {
vhost_interrupt.push(EventFd::new().map_err(Error::VhostIrqCreate)?);
}
let (request_socket, response_socket) = create_control_sockets();
Ok(Net {
workers_kill_evt: Some(kill_evt.try_clone().map_err(Error::CloneKillEventFd)?),
kill_evt,
worker_thread: None,
tap: Some(tap),
vhost_net_handle: Some(vhost_net_handle),
vhost_interrupt: Some(vhost_interrupt),
avail_features,
acked_features: 0u64,
request_socket,
response_socket,
})
}
}
impl<T, U> Drop for Net<T, U>
where
T: TapT,
U: VhostNetT<T>,
{
fn drop(&mut self) {
// Only kill the child if it claimed its eventfd.
if self.workers_kill_evt.is_none() {
// Ignore the result because there is nothing we can do about it.
let _ = self.kill_evt.write(1);
}
if let Some(worker_thread) = self.worker_thread.take() {
let _ = worker_thread.join();
}
}
}
impl<T, U> VirtioDevice for Net<T, U>
where
T: TapT + 'static,
U: VhostNetT<T> + 'static,
{
fn keep_fds(&self) -> Vec<RawFd> {
let mut keep_fds = Vec::new();
if let Some(tap) = &self.tap {
keep_fds.push(tap.as_raw_fd());
}
if let Some(vhost_net_handle) = &self.vhost_net_handle {
keep_fds.push(vhost_net_handle.as_raw_fd());
}
if let Some(vhost_interrupt) = &self.vhost_interrupt {
for vhost_int in vhost_interrupt.iter() {
keep_fds.push(vhost_int.as_raw_fd());
}
}
if let Some(workers_kill_evt) = &self.workers_kill_evt {
keep_fds.push(workers_kill_evt.as_raw_fd());
}
keep_fds.push(self.kill_evt.as_raw_fd());
if let Some(request_socket) = &self.request_socket {
keep_fds.push(request_socket.as_raw_fd());
}
if let Some(response_socket) = &self.response_socket {
keep_fds.push(response_socket.as_raw_fd());
}
keep_fds
}
fn device_type(&self) -> u32 {
TYPE_NET
}
fn queue_max_sizes(&self) -> &[u16] {
QUEUE_SIZES
}
fn features(&self) -> u64 {
self.avail_features
}
fn ack_features(&mut self, value: u64) {
let mut v = value;
// Check if the guest is ACK'ing a feature that we didn't claim to have.
let unrequested_features = v & !self.avail_features;
if unrequested_features != 0 {
warn!("net: virtio net got unknown feature ack: {:x}", v);
// Don't count these features as acked.
v &= !unrequested_features;
}
self.acked_features |= v;
}
fn activate(
&mut self,
_: GuestMemory,
interrupt: Interrupt,
queues: Vec<Queue>,
queue_evts: Vec<EventFd>,
) {
if queues.len() != NUM_QUEUES || queue_evts.len() != NUM_QUEUES {
error!("net: expected {} queues, got {}", NUM_QUEUES, queues.len());
return;
}
if let Some(vhost_net_handle) = self.vhost_net_handle.take() {
if let Some(tap) = self.tap.take() {
if let Some(vhost_interrupt) = self.vhost_interrupt.take() {
if let Some(kill_evt) = self.workers_kill_evt.take() {
let acked_features = self.acked_features;
let socket = if self.response_socket.is_some() {
self.response_socket.take()
} else {
None
};
let worker_result = thread::Builder::new()
.name("vhost_net".to_string())
.spawn(move || {
let mut worker = Worker::new(
queues,
vhost_net_handle,
vhost_interrupt,
interrupt,
acked_features,
kill_evt,
socket,
);
let activate_vqs = |handle: &U| -> Result<()> {
for idx in 0..NUM_QUEUES {
handle
.set_backend(idx, Some(&tap))
.map_err(Error::VhostNetSetBackend)?;
}
Ok(())
};
let cleanup_vqs = |handle: &U| -> Result<()> {
for idx in 0..NUM_QUEUES {
handle
.set_backend(idx, None)
.map_err(Error::VhostNetSetBackend)?;
}
Ok(())
};
let result =
worker.run(queue_evts, QUEUE_SIZES, activate_vqs, cleanup_vqs);
if let Err(e) = result {
error!("net worker thread exited with error: {}", e);
}
(worker, tap)
});
match worker_result {
Err(e) => {
error!("failed to spawn vhost_net worker: {}", e);
return;
}
Ok(join_handle) => {
self.worker_thread = Some(join_handle);
}
}
}
}
}
}
}
fn on_device_sandboxed(&mut self) {
// ignore the error but to log the error. We don't need to do
// anything here because when activate, the other vhost set up
// will be failed to stop the activate thread.
if let Some(vhost_net_handle) = &self.vhost_net_handle {
match vhost_net_handle.set_owner() {
Ok(_) => {}
Err(e) => error!("{}: failed to set owner: {:?}", self.debug_label(), e),
}
}
}
fn control_notify(&self, behavior: MsixStatus) {
if self.worker_thread.is_none() || self.request_socket.is_none() {
return;
}
if let Some(socket) = &self.request_socket {
match behavior {
MsixStatus::EntryChanged(index) => {
if let Err(e) = socket.send(&VhostDevRequest::MsixEntryChanged(index)) {
error!(
"{} failed to send VhostMsixEntryChanged request for entry {}: {:?}",
self.debug_label(),
index,
e
);
return;
}
if let Err(e) = socket.recv() {
error!("{} failed to receive VhostMsixEntryChanged response for entry {}: {:?}", self.debug_label(), index, e);
}
}
MsixStatus::Changed => {
if let Err(e) = socket.send(&VhostDevRequest::MsixChanged) {
error!(
"{} failed to send VhostMsixChanged request: {:?}",
self.debug_label(),
e
);
return;
}
if let Err(e) = socket.recv() {
error!(
"{} failed to receive VhostMsixChanged response {:?}",
self.debug_label(),
e
);
}
}
_ => {}
}
}
}
fn reset(&mut self) -> bool {
// Only kill the child if it claimed its eventfd.
if self.workers_kill_evt.is_none() && self.kill_evt.write(1).is_err() {
error!("{}: failed to notify the kill event", self.debug_label());
return false;
}
if let Some(worker_thread) = self.worker_thread.take() {
match worker_thread.join() {
Err(_) => {
error!("{}: failed to get back resources", self.debug_label());
return false;
}
Ok((worker, tap)) => {
self.vhost_net_handle = Some(worker.vhost_handle);
self.tap = Some(tap);
self.vhost_interrupt = Some(worker.vhost_interrupt);
self.workers_kill_evt = Some(worker.kill_evt);
self.response_socket = worker.response_socket;
return true;
}
}
}
false
}
}
#[cfg(test)]
pub mod tests {
use super::*;
use crate::virtio::VIRTIO_MSI_NO_VECTOR;
use net_util::fakes::FakeTap;
use std::result;
use std::sync::atomic::AtomicUsize;
use std::sync::Arc;
use sys_util::{GuestAddress, GuestMemory, GuestMemoryError};
use vhost::net::fakes::FakeNet;
fn create_guest_memory() -> result::Result<GuestMemory, GuestMemoryError> {
let start_addr1 = GuestAddress(0x0);
let start_addr2 = GuestAddress(0x1000);
GuestMemory::new(&vec![(start_addr1, 0x1000), (start_addr2, 0x4000)])
}
fn create_net_common() -> Net<FakeTap, FakeNet<FakeTap>> {
let guest_memory = create_guest_memory().unwrap();
Net::<FakeTap, FakeNet<FakeTap>>::new(
Ipv4Addr::new(127, 0, 0, 1),
Ipv4Addr::new(255, 255, 255, 0),
"de:21:e8:47:6b:6a".parse().unwrap(),
&guest_memory,
)
.unwrap()
}
#[test]
fn create_net() {
create_net_common();
}
#[test]
fn keep_fds() {
let net = create_net_common();
let fds = net.keep_fds();
assert!(fds.len() >= 1, "We should have gotten at least one fd");
}
#[test]
fn features() {
let net = create_net_common();
assert_eq!(net.features(), 5117103235);
}
#[test]
fn ack_features() {
let mut net = create_net_common();
// Just testing that we don't panic, for now
net.ack_features(1);
net.ack_features(1 << 32);
}
#[test]
fn activate() {
let mut net = create_net_common();
let guest_memory = create_guest_memory().unwrap();
// Just testing that we don't panic, for now
net.activate(
guest_memory,
Interrupt::new(
Arc::new(AtomicUsize::new(0)),
EventFd::new().unwrap(),
EventFd::new().unwrap(),
None,
VIRTIO_MSI_NO_VECTOR,
),
vec![Queue::new(1)],
vec![EventFd::new().unwrap()],
);
}
}