summary refs log tree commit diff
path: root/src/hw/virtio/vhost_net.rs
diff options
context:
space:
mode:
authorStephen Barber <smbarber@chromium.org>2017-08-07 16:43:56 -0700
committerchrome-bot <chrome-bot@chromium.org>2017-08-09 11:47:55 -0700
commitd579e3cd49c50bd06465c277f030ada3217e2a24 (patch)
tree89b689998891e2e2afb57bc8a16a3b3b0edbcd59 /src/hw/virtio/vhost_net.rs
parent604d989d7e8f7a159e160b86302d41aa2ddd4f35 (diff)
downloadcrosvm-d579e3cd49c50bd06465c277f030ada3217e2a24.tar
crosvm-d579e3cd49c50bd06465c277f030ada3217e2a24.tar.gz
crosvm-d579e3cd49c50bd06465c277f030ada3217e2a24.tar.bz2
crosvm-d579e3cd49c50bd06465c277f030ada3217e2a24.tar.lz
crosvm-d579e3cd49c50bd06465c277f030ada3217e2a24.tar.xz
crosvm-d579e3cd49c50bd06465c277f030ada3217e2a24.tar.zst
crosvm-d579e3cd49c50bd06465c277f030ada3217e2a24.zip
crosvm: rename Net virtio device to VhostNet
BUG=chromium:703920
TEST=networking still works

Change-Id: I28517cc61a572998cd57868c2dbccec247140a58
Signed-off-by: Stephen Barber <smbarber@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/604936
Reviewed-by: Zach Reizner <zachr@chromium.org>
Diffstat (limited to 'src/hw/virtio/vhost_net.rs')
-rw-r--r--src/hw/virtio/vhost_net.rs347
1 files changed, 347 insertions, 0 deletions
diff --git a/src/hw/virtio/vhost_net.rs b/src/hw/virtio/vhost_net.rs
new file mode 100644
index 0000000..e1304f1
--- /dev/null
+++ b/src/hw/virtio/vhost_net.rs
@@ -0,0 +1,347 @@
+// 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::raw::*;
+use std::os::unix::io::{AsRawFd, RawFd};
+use std::sync::Arc;
+use std::sync::atomic::{AtomicUsize, Ordering};
+use std::thread::spawn;
+
+use net_sys;
+use net_util::{Tap, Error as TapError};
+use sys_util::{Error as SysError, EventFd, GuestMemory, Poller};
+use vhost::{VhostNet as VhostNetHandle, Error as VhostError};
+use virtio_sys::{vhost, virtio_net};
+use virtio_sys::virtio_net::virtio_net_hdr_mrg_rxbuf;
+
+use super::{VirtioDevice, Queue, INTERRUPT_STATUS_USED_RING, TYPE_NET};
+
+const QUEUE_SIZE: u16 = 256;
+const QUEUE_SIZES: &'static [u16] = &[QUEUE_SIZE, QUEUE_SIZE];
+
+#[derive(Debug)]
+pub enum VhostNetError {
+    /// Creating kill eventfd failed.
+    CreateKillEventFd(SysError),
+    /// Cloning kill eventfd failed.
+    CloneKillEventFd(SysError),
+    /// Open tap device failed.
+    TapOpen(TapError),
+    /// Setting tap IP failed.
+    TapSetIp(TapError),
+    /// Setting tap netmask failed.
+    TapSetNetmask(TapError),
+    /// Setting tap interface offload flags failed.
+    TapSetOffload(TapError),
+    /// Setting vnet header size failed.
+    TapSetVnetHdrSize(TapError),
+    /// Enabling tap interface failed.
+    TapEnable(TapError),
+    /// Open vhost-net device failed.
+    VhostOpen(VhostError),
+    /// Set owner failed.
+    VhostSetOwner(VhostError),
+    /// Get features failed.
+    VhostGetFeatures(VhostError),
+    /// Set features failed.
+    VhostSetFeatures(VhostError),
+    /// Set mem table failed.
+    VhostSetMemTable(VhostError),
+    /// Set vring num failed.
+    VhostSetVringNum(VhostError),
+    /// Set vring addr failed.
+    VhostSetVringAddr(VhostError),
+    /// Set vring base failed.
+    VhostSetVringBase(VhostError),
+    /// Set vring call failed.
+    VhostSetVringCall(VhostError),
+    /// Set vring kick failed.
+    VhostSetVringKick(VhostError),
+    /// Net set backend failed.
+    VhostNetSetBackend(VhostError),
+    /// Failed to create vhost eventfd.
+    VhostIrqCreate(SysError),
+    /// Failed to read vhost eventfd.
+    VhostIrqRead(SysError),
+    /// Error while polling for events.
+    PollError(SysError),
+}
+
+struct Worker {
+    queues: Vec<Queue>,
+    tap: Tap,
+    vhost_net_handle: VhostNetHandle,
+    vhost_interrupt: EventFd,
+    interrupt_status: Arc<AtomicUsize>,
+    interrupt_evt: EventFd,
+    acked_features: u64,
+}
+
+impl Worker {
+    fn signal_used_queue(&self) {
+        self.interrupt_status
+            .fetch_or(INTERRUPT_STATUS_USED_RING as usize, Ordering::SeqCst);
+        self.interrupt_evt.write(1).unwrap();
+    }
+
+    fn run(&mut self, queue_evts: Vec<EventFd>, kill_evt: EventFd) -> Result<(), VhostNetError> {
+        // Preliminary setup for vhost net.
+        self.vhost_net_handle
+            .set_owner()
+            .map_err(VhostNetError::VhostSetOwner)?;
+
+        let avail_features = self.vhost_net_handle
+            .get_features()
+            .map_err(VhostNetError::VhostGetFeatures)?;
+
+        let features: c_ulonglong = self.acked_features & avail_features;
+        self.vhost_net_handle
+            .set_features(features)
+            .map_err(VhostNetError::VhostSetFeatures)?;
+
+        self.vhost_net_handle
+            .set_mem_table()
+            .map_err(VhostNetError::VhostSetMemTable)?;
+
+        for (queue_index, ref queue) in self.queues.iter().enumerate() {
+            self.vhost_net_handle
+                .set_vring_num(queue_index, queue.max_size)
+                .map_err(VhostNetError::VhostSetVringNum)?;
+
+            self.vhost_net_handle
+                .set_vring_addr(QUEUE_SIZES[queue_index],
+                                queue.actual_size(),
+                                queue_index,
+                                0,
+                                queue.desc_table,
+                                queue.used_ring,
+                                queue.avail_ring,
+                                None)
+                .map_err(VhostNetError::VhostSetVringAddr)?;
+            self.vhost_net_handle
+                .set_vring_base(queue_index, 0)
+                .map_err(VhostNetError::VhostSetVringBase)?;
+            self.vhost_net_handle
+                .set_vring_call(queue_index, &self.vhost_interrupt)
+                .map_err(VhostNetError::VhostSetVringCall)?;
+            self.vhost_net_handle
+                .set_vring_kick(queue_index, &queue_evts[queue_index])
+                .map_err(VhostNetError::VhostSetVringKick)?;
+            self.vhost_net_handle
+                .net_set_backend(queue_index, &self.tap)
+                .map_err(VhostNetError::VhostNetSetBackend)?;
+        }
+
+        const VHOST_IRQ: u32 = 1;
+        const KILL: u32 = 2;
+
+        let mut poller = Poller::new(2);
+
+        'poll: loop {
+            let tokens = match poller.poll(&[(VHOST_IRQ, &self.vhost_interrupt),
+                                             (KILL, &kill_evt)]) {
+                Ok(v) => v,
+                Err(e) => return Err(VhostNetError::PollError(e)),
+            };
+
+            let mut needs_interrupt = false;
+            for &token in tokens {
+                match token {
+                    VHOST_IRQ => {
+                        needs_interrupt = true;
+                        self.vhost_interrupt
+                            .read()
+                            .map_err(VhostNetError::VhostIrqRead)?;
+                    }
+                    KILL => break 'poll,
+                    _ => unreachable!(),
+                }
+            }
+            if needs_interrupt {
+                self.signal_used_queue();
+            }
+        }
+        Ok(())
+    }
+}
+
+pub struct VhostNet {
+    workers_kill_evt: Option<EventFd>,
+    kill_evt: EventFd,
+    tap: Option<Tap>,
+    vhost_net_handle: Option<VhostNetHandle>,
+    vhost_interrupt: Option<EventFd>,
+    avail_features: u64,
+    acked_features: u64,
+}
+
+impl VhostNet {
+    /// Create a new virtio network device with the given IP address and
+    /// netmask.
+    pub fn new(ip_addr: Ipv4Addr,
+               netmask: Ipv4Addr,
+               mem: &GuestMemory)
+               -> Result<VhostNet, VhostNetError> {
+        let kill_evt = EventFd::new().map_err(VhostNetError::CreateKillEventFd)?;
+
+        let tap = Tap::new().map_err(VhostNetError::TapOpen)?;
+        tap.set_ip_addr(ip_addr)
+            .map_err(VhostNetError::TapSetIp)?;
+        tap.set_netmask(netmask)
+            .map_err(VhostNetError::TapSetNetmask)?;
+
+        // 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(VhostNetError::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_hdr_mrg_rxbuf>() as i32;
+        tap.set_vnet_hdr_size(vnet_hdr_size)
+            .map_err(VhostNetError::TapSetVnetHdrSize)?;
+
+        tap.enable().map_err(VhostNetError::TapEnable)?;
+        let vhost_net_handle = VhostNetHandle::new(mem)
+            .map_err(VhostNetError::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 << vhost::VIRTIO_RING_F_INDIRECT_DESC |
+            1 << vhost::VIRTIO_RING_F_EVENT_IDX |
+            1 << vhost::VIRTIO_F_NOTIFY_ON_EMPTY |
+            1 << vhost::VIRTIO_F_VERSION_1;
+
+        Ok(VhostNet {
+               workers_kill_evt: Some(kill_evt
+                                          .try_clone()
+                                          .map_err(VhostNetError::CloneKillEventFd)?),
+               kill_evt: kill_evt,
+               tap: Some(tap),
+               vhost_net_handle: Some(vhost_net_handle),
+               vhost_interrupt: Some(EventFd::new().map_err(VhostNetError::VhostIrqCreate)?),
+               avail_features: avail_features,
+               acked_features: 0u64,
+           })
+    }
+}
+
+impl Drop for VhostNet {
+    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);
+        }
+    }
+}
+
+impl VirtioDevice for VhostNet {
+    fn keep_fds(&self) -> Vec<RawFd> {
+        let mut keep_fds = Vec::new();
+
+        if let Some(ref tap) = self.tap {
+            keep_fds.push(tap.as_raw_fd());
+        }
+
+        if let Some(ref vhost_net_handle) = self.vhost_net_handle {
+            keep_fds.push(vhost_net_handle.as_raw_fd());
+        }
+
+        if let Some(ref vhost_interrupt) = self.vhost_interrupt {
+            keep_fds.push(vhost_interrupt.as_raw_fd());
+        }
+
+        if let Some(ref workers_kill_evt) = self.workers_kill_evt {
+            keep_fds.push(workers_kill_evt.as_raw_fd());
+        }
+
+        keep_fds
+    }
+
+    fn device_type(&self) -> u32 {
+        TYPE_NET
+    }
+
+    fn queue_max_sizes(&self) -> &[u16] {
+        QUEUE_SIZES
+    }
+
+    fn features(&self, page: u32) -> u32 {
+        match page {
+            0 => self.avail_features as u32,
+            1 => (self.avail_features >> 32) as u32,
+            _ => {
+                warn!("net: virtio net got request for features page: {}", page);
+                0u32
+            }
+        }
+    }
+
+    fn ack_features(&mut self, page: u32, value: u32) {
+        let mut v = match page {
+            0 => value as u64,
+            1 => (value as u64) << 32,
+            _ => {
+                warn!("net: virtio net device cannot ack unknown feature page: {}",
+                      page);
+                0u64
+            }
+        };
+
+        // 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_evt: EventFd,
+                status: Arc<AtomicUsize>,
+                queues: Vec<Queue>,
+                queue_evts: Vec<EventFd>) {
+        if queues.len() != 2 || queue_evts.len() != 2 {
+            error!("net: expected 2 queues, got {}", 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;
+                        spawn(move || {
+                            let mut worker = Worker {
+                                queues: queues,
+                                tap: tap,
+                                vhost_net_handle: vhost_net_handle,
+                                vhost_interrupt: vhost_interrupt,
+                                interrupt_status: status,
+                                interrupt_evt: interrupt_evt,
+                                acked_features: acked_features,
+                            };
+                            let result = worker.run(queue_evts, kill_evt);
+                            if let Err(e) = result {
+                                error!("net worker thread exited with error: {:?}", e);
+                            }
+                        });
+                    }
+                }
+            }
+        }
+    }
+}