diff --git a/src/main.rs b/src/main.rs old mode 100644 new mode 100755 index ef8dbc221..1ac3c28b6 --- a/src/main.rs +++ b/src/main.rs @@ -55,6 +55,12 @@ fn main() { .help("Amount of RAM (in MB)") .takes_value(true), ) + .arg( + Arg::with_name("rng") + .long("rng") + .help("Path to entropy source") + .default_value("/dev/urandom"), + ) .get_matches(); let kernel_arg = cmd_arguments @@ -79,6 +85,11 @@ fn main() { net_params = Some(net.to_string()); } + let rng_path = match cmd_arguments.occurrences_of("rng") { + 0 => None, + _ => Some(cmd_arguments.value_of("rng").unwrap().to_string()), + }; + let mut vcpus = DEFAULT_VCPUS; if let Some(cpus) = cmd_arguments.value_of("cpus") { vcpus = cpus.parse::().unwrap(); @@ -92,8 +103,16 @@ fn main() { println!("VM [{} vCPUS {} MB of memory]", vcpus, memory); println!("Booting {:?}...", kernel_path); - let vm_config = - VmConfig::new(kernel_path, disk_path, cmdline, net_params, vcpus, memory).unwrap(); + let vm_config = VmConfig::new( + kernel_path, + disk_path, + rng_path, + cmdline, + net_params, + vcpus, + memory, + ) + .unwrap(); vmm::boot_kernel(vm_config).unwrap(); } diff --git a/vm-virtio/src/lib.rs b/vm-virtio/src/lib.rs old mode 100644 new mode 100755 index 3b5a516ab..2f922bff7 --- a/vm-virtio/src/lib.rs +++ b/vm-virtio/src/lib.rs @@ -24,6 +24,7 @@ mod block; mod device; pub mod net; mod queue; +mod rng; pub mod transport; @@ -31,6 +32,7 @@ pub use self::block::*; pub use self::device::*; pub use self::net::*; pub use self::queue::*; +pub use self::rng::*; const DEVICE_INIT: u32 = 0x00; const DEVICE_ACKNOWLEDGE: u32 = 0x01; diff --git a/vm-virtio/src/rng.rs b/vm-virtio/src/rng.rs new file mode 100755 index 000000000..fa6a9e20d --- /dev/null +++ b/vm-virtio/src/rng.rs @@ -0,0 +1,279 @@ +// 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 epoll; +use libc::EFD_NONBLOCK; +use std; +use std::fs::File; +use std::io; +use std::os::unix::io::AsRawFd; +use std::result; +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::Arc; +use std::thread; + +use super::Error as DeviceError; +use super::{ + ActivateError, ActivateResult, DeviceEventT, Queue, VirtioDevice, VirtioDeviceType, + INTERRUPT_STATUS_USED_RING, VIRTIO_F_VERSION_1, +}; + +use vm_memory::{Bytes, GuestMemoryMmap}; +use vmm_sys_util::EventFd; + +const QUEUE_SIZE: u16 = 256; +const NUM_QUEUES: usize = 1; +const QUEUE_SIZES: &[u16] = &[QUEUE_SIZE]; + +// New descriptors are pending on the virtio queue. +const QUEUE_AVAIL_EVENT: DeviceEventT = 0; +// The device has been dropped. +const KILL_EVENT: DeviceEventT = 1; + +struct RngEpollHandler { + queues: Vec, + mem: GuestMemoryMmap, + random_file: File, + interrupt_status: Arc, + interrupt_evt: EventFd, + queue_evt: EventFd, + kill_evt: EventFd, +} + +impl RngEpollHandler { + fn process_queue(&mut self) -> bool { + let queue = &mut self.queues[0]; + + let mut used_desc_heads = [(0, 0); QUEUE_SIZE as usize]; + let mut used_count = 0; + for avail_desc in queue.iter(&self.mem) { + let mut len = 0; + + // Drivers can only read from the random device. + if avail_desc.is_write_only() { + // Fill the read with data from the random device on the host. + if self + .mem + .read_from( + avail_desc.addr, + &mut self.random_file, + avail_desc.len as usize, + ) + .is_ok() + { + len = avail_desc.len; + } + } + + used_desc_heads[used_count] = (avail_desc.index, len); + used_count += 1; + } + + for &(desc_index, len) in &used_desc_heads[..used_count] { + queue.add_used(&self.mem, desc_index, len); + } + used_count > 0 + } + + fn signal_used_queue(&self) -> result::Result<(), DeviceError> { + self.interrupt_status + .fetch_or(INTERRUPT_STATUS_USED_RING as usize, Ordering::SeqCst); + self.interrupt_evt.write(1).map_err(|e| { + error!("Failed to signal used queue: {:?}", e); + DeviceError::FailedSignalingUsedQueue(e) + }) + } + + fn run(&mut self) -> result::Result<(), DeviceError> { + // Create the epoll file descriptor + let epoll_fd = epoll::create(true).map_err(DeviceError::EpollCreateFd)?; + + // Add events + epoll::ctl( + epoll_fd, + epoll::ControlOptions::EPOLL_CTL_ADD, + self.queue_evt.as_raw_fd(), + epoll::Event::new(epoll::Events::EPOLLIN, u64::from(QUEUE_AVAIL_EVENT)), + ) + .map_err(DeviceError::EpollCtl)?; + epoll::ctl( + epoll_fd, + epoll::ControlOptions::EPOLL_CTL_ADD, + self.kill_evt.as_raw_fd(), + epoll::Event::new(epoll::Events::EPOLLIN, u64::from(KILL_EVENT)), + ) + .map_err(DeviceError::EpollCtl)?; + + const EPOLL_EVENTS_LEN: usize = 100; + let mut events = vec![epoll::Event::new(epoll::Events::empty(), 0); EPOLL_EVENTS_LEN]; + + 'epoll: loop { + let num_events = + epoll::wait(epoll_fd, -1, &mut events[..]).map_err(DeviceError::EpollWait)?; + + for event in events.iter().take(num_events) { + let ev_type = event.data as u16; + + match ev_type { + QUEUE_AVAIL_EVENT => { + if let Err(e) = self.queue_evt.read() { + error!("Failed to get queue event: {:?}", e); + break 'epoll; + } else if self.process_queue() { + if let Err(e) = self.signal_used_queue() { + error!("Failed to signal used queue: {:?}", e); + break 'epoll; + } + } + } + KILL_EVENT => { + debug!("KILL_EVENT received, stopping epoll loop"); + break 'epoll; + } + _ => { + error!("Unknown event for virtio-block"); + } + } + } + } + + Ok(()) + } +} + +/// Virtio device for exposing entropy to the guest OS through virtio. +pub struct Rng { + kill_evt: Option, + random_file: Option, + avail_features: u64, + acked_features: u64, +} + +impl Rng { + /// Create a new virtio rng device that gets random data from /dev/urandom. + pub fn new(path: &str) -> io::Result { + let random_file = File::open(path)?; + let avail_features = 1u64 << VIRTIO_F_VERSION_1; + + Ok(Rng { + kill_evt: None, + random_file: Some(random_file), + avail_features, + acked_features: 0u64, + }) + } +} + +impl Drop for Rng { + fn drop(&mut self) { + if let Some(kill_evt) = self.kill_evt.take() { + // Ignore the result because there is nothing we can do about it. + let _ = kill_evt.write(1); + } + } +} + +impl VirtioDevice for Rng { + fn device_type(&self) -> u32 { + VirtioDeviceType::TYPE_RNG as u32 + } + + fn queue_max_sizes(&self) -> &[u16] { + QUEUE_SIZES + } + + fn features(&self, page: u32) -> u32 { + match page { + // Get the lower 32-bits of the features bitfield. + 0 => self.avail_features as u32, + // Get the upper 32-bits of the features bitfield. + 1 => (self.avail_features >> 32) as u32, + _ => { + warn!("Received request for unknown features page."); + 0u32 + } + } + } + + fn ack_features(&mut self, page: u32, value: u32) { + let mut v = match page { + 0 => u64::from(value), + 1 => u64::from(value) << 32, + _ => { + warn!("Cannot acknowledge unknown features 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!("Received acknowledge request for unknown feature."); + + // Don't count these features as acked. + v &= !unrequested_features; + } + self.acked_features |= v; + } + + fn read_config(&self, _offset: u64, _data: &mut [u8]) { + warn!("No currently device specific configration defined"); + } + + fn write_config(&mut self, _offset: u64, _data: &[u8]) { + warn!("No currently device specific configration defined"); + } + + fn activate( + &mut self, + mem: GuestMemoryMmap, + interrupt_evt: EventFd, + status: Arc, + queues: Vec, + mut queue_evts: Vec, + ) -> ActivateResult { + if queues.len() != NUM_QUEUES || queue_evts.len() != NUM_QUEUES { + error!( + "Cannot perform activate. Expected {} queue(s), got {}", + NUM_QUEUES, + queues.len() + ); + return Err(ActivateError::BadActivate); + } + + let (self_kill_evt, kill_evt) = + match EventFd::new(EFD_NONBLOCK).and_then(|e| Ok((e.try_clone()?, e))) { + Ok(v) => v, + Err(e) => { + error!("failed creating kill EventFd pair: {}", e); + return Err(ActivateError::BadActivate); + } + }; + self.kill_evt = Some(self_kill_evt); + + if let Some(random_file) = self.random_file.take() { + let mut handler = RngEpollHandler { + queues, + mem, + random_file, + interrupt_status: status, + interrupt_evt, + queue_evt: queue_evts.remove(0), + kill_evt, + }; + + let worker_result = thread::Builder::new() + .name("virtio_rng".to_string()) + .spawn(move || handler.run()); + + if let Err(e) = worker_result { + error!("failed to spawn virtio_rng worker: {}", e); + return Err(ActivateError::BadActivate);; + } + + return Ok(()); + } + Err(ActivateError::BadActivate) + } +} diff --git a/vmm/src/vm.rs b/vmm/src/vm.rs index b28da8989..714fbd964 100755 --- a/vmm/src/vm.rs +++ b/vmm/src/vm.rs @@ -158,6 +158,9 @@ pub enum Error { /// Cannot create virtio-net device CreateVirtioNet(vm_virtio::net::Error), + /// Cannot create virtio-rng device + CreateVirtioRng(io::Error), + /// Cannot create the system allocator CreateSystemAllocator, @@ -225,6 +228,7 @@ impl Vcpu { pub struct VmConfig<'a> { kernel_path: &'a Path, disk_path: &'a Path, + rng_path: Option, cmdline: cmdline::Cmdline, cmdline_addr: GuestAddress, net_params: Option, @@ -236,6 +240,7 @@ impl<'a> VmConfig<'a> { pub fn new( kernel_path: &'a Path, disk_path: &'a Path, + rng_path: Option, cmdline_str: String, net_params: Option, vcpus: u8, @@ -247,6 +252,7 @@ impl<'a> VmConfig<'a> { Ok(VmConfig { kernel_path, disk_path, + rng_path, cmdline, cmdline_addr: CMDLINE_OFFSET, net_params, @@ -357,6 +363,21 @@ impl DeviceManager { } } + // Add virtio-rng if required + if let Some(rng_path) = &vm_cfg.rng_path { + let virtio_rng_device = + vm_virtio::Rng::new(rng_path).map_err(Error::CreateVirtioRng)?; + + DeviceManager::add_virtio_pci_device( + Box::new(virtio_rng_device), + memory.clone(), + allocator, + vm_fd, + &mut pci_root, + &mut mmio_bus, + )?; + } + let pci = Arc::new(Mutex::new(PciConfigIo::new(pci_root))); Ok(DeviceManager {