diff --git a/Cargo.lock b/Cargo.lock index 0fe53939fa4..04ed61604b3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -167,8 +167,6 @@ dependencies = [ "rate_limiter 0.1.0", "sys_util 0.1.0", "tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "vhost_backend 0.1.0", - "vhost_gen 0.1.0", "virtio_gen 0.1.0", ] @@ -995,24 +993,6 @@ name = "version_check" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "vhost_backend" -version = "0.1.0" -dependencies = [ - "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", - "memory_model 0.1.0", - "sys_util 0.1.0", - "vhost_gen 0.1.0", -] - -[[package]] -name = "vhost_gen" -version = "0.1.0" -dependencies = [ - "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", - "sys_util 0.1.0", -] - [[package]] name = "virtio_gen" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 7dea241e4f2..36bc1c0f8eb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,7 +25,4 @@ panic = "abort" lto = true panic = "abort" -[features] -vsock = ["api_server/vsock", "jailer/vsock"] - [workspace] diff --git a/api_server/Cargo.toml b/api_server/Cargo.toml index b90969c9148..c9836e8e003 100644 --- a/api_server/Cargo.toml +++ b/api_server/Cargo.toml @@ -26,6 +26,3 @@ kernel = { path = "../kernel" } memory_model = { path = "../memory_model" } net_util = { path = "../net_util" } rate_limiter = { path = "../rate_limiter" } - -[features] -vsock = ["vmm/vsock"] diff --git a/api_server/src/http_service.rs b/api_server/src/http_service.rs index 8ee7010bb80..93dc6685de6 100644 --- a/api_server/src/http_service.rs +++ b/api_server/src/http_service.rs @@ -25,7 +25,6 @@ use vmm::vmm_config::instance_info::InstanceInfo; use vmm::vmm_config::logger::LoggerConfig; use vmm::vmm_config::machine_config::VmConfig; use vmm::vmm_config::net::{NetworkInterfaceConfig, NetworkInterfaceUpdateConfig}; -#[cfg(feature = "vsock")] use vmm::vmm_config::vsock::VsockDeviceConfig; use vmm::VmmAction; @@ -365,7 +364,6 @@ fn parse_netif_req<'a>(path: &'a str, method: Method, body: &Chunk) -> Result<'a } } -#[cfg(feature = "vsock")] // Turns a GET/PUT /vsocks HTTP request into a ParsedRequest. fn parse_vsocks_req<'a>(path: &'a str, method: Method, body: &Chunk) -> Result<'a, ParsedRequest> { let path_tokens: Vec<&str> = path[1..].split_terminator('/').collect(); @@ -432,7 +430,6 @@ fn parse_request<'a>(method: Method, path: &'a str, body: &Chunk) -> Result<'a, "machine-config" => parse_machine_config_req(path, method, body), "network-interfaces" => parse_netif_req(path, method, body), "mmds" => parse_mmds_request(path, method, body), - #[cfg(feature = "vsock")] "vsocks" => parse_vsocks_req(path, method, body), _ => Err(Error::InvalidPathMethod(path, method)), } diff --git a/api_server/src/request/mod.rs b/api_server/src/request/mod.rs index 681fa434023..591bdee50f5 100644 --- a/api_server/src/request/mod.rs +++ b/api_server/src/request/mod.rs @@ -7,7 +7,6 @@ pub mod drive; pub mod logger; pub mod machine_configuration; pub mod net; -#[cfg(feature = "vsock")] pub mod vsock; use serde_json::Value; diff --git a/api_server/src/request/vsock.rs b/api_server/src/request/vsock.rs index 14dd0f16ee4..bda42d4ba86 100644 --- a/api_server/src/request/vsock.rs +++ b/api_server/src/request/vsock.rs @@ -17,7 +17,7 @@ impl IntoParsedRequest for VsockDeviceConfig { _: Method, ) -> result::Result { let id_from_path = id_from_path.unwrap_or_default(); - if id_from_path != self.id.as_str() { + if id_from_path != self.vsock_id.as_str() { return Err(String::from( "The id from the path does not match the id from the body!", )); @@ -38,8 +38,9 @@ mod tests { #[test] fn test_vsock_into_parsed_request() { let vsock = VsockDeviceConfig { - id: String::from("foo"), + vsock_id: String::from("foo"), guest_cid: 42, + uds_path: "vsock.sock".to_string(), }; assert!(vsock .clone() diff --git a/api_server/swagger/firecracker-experimental.yaml b/api_server/swagger/firecracker-experimental.yaml deleted file mode 100644 index 1cb42891c12..00000000000 --- a/api_server/swagger/firecracker-experimental.yaml +++ /dev/null @@ -1,661 +0,0 @@ -swagger: "2.0" -info: - title: Firecracker API - description: RESTful public-facing API. - The API is accessible through HTTP calls on specific URLs - carrying JSON modeled data. - The transport medium is a Unix Domain Socket. - This API has definitions for experimental features like vsock. - version: 0.17.0 - termsOfService: "" - contact: - email: "compute-capsule@amazon.com" - license: - name: "Apache 2.0" - url: "http://www.apache.org/licenses/LICENSE-2.0.html" - -host: "localhost" -basePath: "/" - -schemes: - - http -consumes: - - application/json -produces: - - application/json - -paths: - /: - get: - summary: Returns general information about an instance. - operationId: describeInstance - responses: - 200: - description: The instance information - schema: - $ref: "#/definitions/InstanceInfo" - default: - description: Internal Server Error - schema: - $ref: "#/definitions/Error" - - /actions: - put: - summary: Creates a synchronous action. - operationId: createSyncAction - parameters: - - name: info - in: body - required: true - schema: - $ref: "#/definitions/InstanceActionInfo" - responses: - 204: - description: The update was successful - 400: - description: The action cannot be executed due to bad input - schema: - $ref: "#/definitions/Error" - default: - description: Internal Server Error - schema: - $ref: "#/definitions/Error" - - /boot-source: - put: - summary: Creates or updates the boot source. - description: - Creates new boot source if one does not already exist, otherwise updates it. - Will fail if update is not possible. - Note that the only currently supported boot source is LocalImage. - operationId: putGuestBootSource - parameters: - - name: body - in: body - description: Guest boot source properties - required: true - schema: - $ref: "#/definitions/BootSource" - responses: - 204: - description: Boot source created/updated - 400: - description: Boot source cannot be created due to bad input - schema: - $ref: "#/definitions/Error" - default: - description: Internal server error - schema: - $ref: "#/definitions/Error" - - /drives/{drive_id}: - put: - summary: Creates or updates a drive. - description: - Creates new drive with ID specified by drive_id path parameter. - If a drive with the specified ID already exists, updates its state based on new input. - Will fail if update is not possible. - operationId: putGuestDriveByID - parameters: - - name: drive_id - in: path - description: The id of the guest drive - required: true - type: string - - name: body - in: body - description: Guest drive properties - required: true - schema: - $ref: "#/definitions/Drive" - responses: - 204: - description: Drive created/updated - 400: - description: Drive cannot be created/updated due to bad input - schema: - $ref: "#/definitions/Error" - default: - description: Internal server error. - schema: - $ref: "#/definitions/Error" - patch: - summary: Updates the properties of a drive. - description: - Updates the properties of the drive with the ID specified by drive_id path parameter. - Will fail if update is not possible. - operationId: patchGuestDriveByID - parameters: - - name: drive_id - in: path - description: The id of the guest drive - required: true - type: string - - name: body - in: body - description: Guest drive properties - required: true - schema: - $ref: "#/definitions/PartialDrive" - responses: - 204: - description: Drive updated - 400: - description: Drive cannot be updated due to bad input - schema: - $ref: "#/definitions/Error" - default: - description: Internal server error. - schema: - $ref: "#/definitions/Error" - - /logger: - put: - summary: Initializes the logger by specifying two named pipes (i.e. for the logs and metrics output). - operationId: putLogger - parameters: - - name: body - in: body - description: Logging system description - required: true - schema: - $ref: "#/definitions/Logger" - responses: - 204: - description: Logger created. - 400: - description: Logger cannot be initialized due to bad input. - schema: - $ref: "#/definitions/Error" - default: - description: Internal server error. - schema: - $ref: "#/definitions/Error" - - /machine-config: - get: - summary: Gets the machine configuration of the VM. - description: - Gets the machine configuration of the VM. When called before the PUT operation, it - will return the default values for the vCPU count (=1), memory size (=128 MiB). - By default Hyperthreading is disabled and there is no CPU Template. - operationId: getMachineConfiguration - responses: - 200: - description: OK - schema: - $ref: "#/definitions/MachineConfiguration" - default: - description: Internal server error - schema: - $ref: "#/definitions/Error" - - put: - summary: Updates the Machine Configuration of the VM. - description: - Updates the Virtual Machine Configuration with the specified input. - Firecracker starts with default values for vCPU count (=1) and memory size (=128 MiB). - With Hyperthreading enabled, the vCPU count is restricted to be 1 or an even number, - otherwise there are no restrictions regarding the vCPU count. - If any of the parameters has an incorrect value, the whole update fails. - operationId: putMachineConfiguration - parameters: - - name: body - in: body - description: Machine Configuration Parameters - schema: - $ref: "#/definitions/MachineConfiguration" - responses: - 204: - description: Machine Configuration created/updated - 400: - description: Machine Configuration cannot be updated due to bad input - schema: - $ref: "#/definitions/Error" - default: - description: Internal server error - schema: - $ref: "#/definitions/Error" - - patch: - summary: Partially updates the Machine Configuration of the VM. - description: - Partially updates the Virtual Machine Configuration with the specified input. - If any of the parameters has an incorrect value, the whole update fails. - operationId: patchMachineConfiguration - parameters: - - name: body - in: body - description: A subset of Machine Configuration Parameters - schema: - $ref: "#/definitions/MachineConfiguration" - responses: - 204: - description: Machine Configuration created/updated - 400: - description: Machine Configuration cannot be updated due to bad input - schema: - $ref: "#/definitions/Error" - default: - description: Internal server error - schema: - $ref: "#/definitions/Error" - - /mmds: - put: - summary: Creates a MMDS (Microvm Metadata Service) data store. - parameters: - - name: body - in: body - description: The MMDS data store as JSON. - schema: - type: object - responses: - 204: - description: MMDS data store created/updated. - 400: - description: MMDS data store cannot be created due to bad input. - schema: - $ref: "#/definitions/Error" - default: - description: Internal server error - schema: - $ref: "#/definitions/Error" - patch: - summary: Updates the MMDS data store. - parameters: - - name: body - in: body - description: The MMDS data store patch JSON. - schema: - type: object - responses: - 204: - description: MMDS data store updated. - 400: - description: MMDS data store cannot be updated due to bad input. - schema: - $ref: "#/definitions/Error" - default: - description: Internal server error - schema: - $ref: "#/definitions/Error" - get: - summary: Get the MMDS data store. - responses: - 200: - description: The MMDS data store JSON. - schema: - type: object - 400: - description: Cannot get the MMDS data store due to bad input. - schema: - $ref: "#/definitions/Error" - default: - description: Internal server error - schema: - $ref: "#/definitions/Error" - - /network-interfaces/{iface_id}: - put: - summary: Creates a network interface. - description: - Creates new network interface with ID specified by iface_id path parameter. - operationId: putGuestNetworkInterfaceByID - parameters: - - name: iface_id - in: path - description: The id of the guest network interface - required: true - type: string - - name: body - in: body - description: Guest network interface properties - required: true - schema: - $ref: "#/definitions/NetworkInterface" - responses: - 204: - description: Network interface created/updated - 400: - description: Network interface cannot be created due to bad input - schema: - $ref: "#/definitions/Error" - default: - description: Internal server error - schema: - $ref: "#/definitions/Error" - patch: - summary: Updates the rate limiters applied to a network interface. - description: - Updates the rate limiters applied to a network interface. - operationId: patchGuestNetworkInterfaceByID - parameters: - - name: iface_id - in: path - description: The id of the guest network interface - required: true - type: string - - name: body - in: body - description: A subset of the guest network interface properties - required: true - schema: - $ref: "#/definitions/PartialNetworkInterface" - responses: - 204: - description: Network interface updated - 400: - description: Network interface cannot be updated due to bad input - schema: - $ref: "#/definitions/Error" - default: - description: Internal server error - schema: - $ref: "#/definitions/Error" - - /vsocks/{id}: - put: - summary: Creates new vsock with ID specified by the id parameter. - description: - If the vsock device with the specified ID already exists, its body will - be updated based on the new input. May fail if update is not possible. - operationId: putGuestVsockByID - parameters: - - name: id - in: path - description: The id of the vsock device - required: true - type: string - - name: body - in: body - description: Guest vsock properties - required: true - schema: - $ref: "#/definitions/Vsock" - responses: - 201: - description: Vsock created - 204: - description: Vsock updated - 400: - description: Vsock cannot be created due to bad input - schema: - $ref: "#/definitions/Error" - default: - description: Internal server error - schema: - $ref: "#/definitions/Error" - -definitions: - BootSource: - type: object - required: - - kernel_image_path - description: - Boot source descriptor. - properties: - kernel_image_path: - type: string - description: Host level path to the kernel image used to boot the guest - boot_args: - type: string - description: Kernel boot arguments - - CpuTemplate: - type: string - description: - The CPU Template defines a set of flags to be disabled from the microvm so that - the features exposed to the guest are the same as in the selected instance type. - enum: - - C3 - - T2 - - Drive: - type: object - required: - - drive_id - - path_on_host - - is_root_device - - is_read_only - properties: - drive_id: - type: string - path_on_host: - type: string - description: Host level path for the guest drive - is_root_device: - type: boolean - partuuid: - type: string - description: - Represents the unique id of the boot partition of this device. It is - optional and it will be taken into account only if the is_root_device - field is true. - is_read_only: - type: boolean - rate_limiter: - $ref: "#/definitions/RateLimiter" - - Error: - type: object - properties: - fault_message: - type: string - description: A description of the error condition - readOnly: true - - InstanceActionInfo: - type: object - description: - Variant wrapper containing the real action. - required: - - action_type - properties: - action_type: - description: Enumeration indicating what type of action is contained in the payload - type: string - enum: - - BlockDeviceRescan - - FlushMetrics - - InstanceStart - - SendCtrlAltDel - payload: - type: string - - InstanceInfo: - type: object - description: - Describes MicroVM instance information. - required: - - id - - state - - vmm_version - properties: - id: - description: MicroVM / instance ID. - type: string - state: - description: - The current detailed state of the Firecracker instance. - This value is read-only for the control-plane. - type: string - enum: - - Uninitialized - - Starting - - Running - - Halting - - Halted - vmm_version: - description: MicroVM hypervisor build version. - type: string - - Logger: - type: object - description: - Describes the configuration option for the logging capability. - required: - - log_fifo - - metrics_fifo - properties: - log_fifo: - type: string - description: The named pipe for the human readable log output. - metrics_fifo: - type: string - description: The named pipe where the JSON-formatted metrics will be flushed. - level: - type: string - description: Set the level. - enum: [Error, Warning, Info, Debug] - default: Warning - show_level: - type: boolean - description: Whether or not to output the level in the logs. - default: false - show_log_origin: - type: boolean - description: Whether or not to include the file path and line number of the log's origin. - default: false - options: - type: array - items: - type: string - description: Additional logging options. Only "LogDirtyPages" is supported. - default: [] - - MachineConfiguration: - type: object - description: - Describes the number of vCPUs, memory size, Hyperthreading capabilities and - the CPU template. - required: - - vcpu_count - - mem_size_mib - - ht_enabled - properties: - vcpu_count: - type: integer - minimum: 1 - maximum: 32 - description: Number of vCPUs (either 1 or an even number) - mem_size_mib: - type: integer - description: Memory size of VM - ht_enabled: - type: boolean - description: Flag for enabling/disabling Hyperthreading - cpu_template: - $ref: "#/definitions/CpuTemplate" - - NetworkInterface: - type: object - description: - Defines a network interface. - required: - - iface_id - - host_dev_name - properties: - iface_id: - type: string - guest_mac: - type: string - host_dev_name: - type: string - description: Host level path for the guest network interface - allow_mmds_requests: - type: boolean - description: - If this field is set, the device model will reply to HTTP GET - requests sent to the MMDS address via this interface. In this case, - both ARP requests for 169.254.169.254 and TCP segments heading to the - same address are intercepted by the device model, and do not reach - the associated TAP device. - rx_rate_limiter: - $ref: "#/definitions/RateLimiter" - tx_rate_limiter: - $ref: "#/definitions/RateLimiter" - - PartialDrive: - type: object - required: - - drive_id - - path_on_host - properties: - drive_id: - type: string - path_on_host: - type: string - description: Host level path for the guest drive - - PartialNetworkInterface: - type: object - description: - Defines a partial network interface structure, used to update the rate limiters - for that interface, after microvm start. - required: - - iface_id - properties: - iface_id: - type: string - rx_rate_limiter: - $ref: "#/definitions/RateLimiter" - tx_rate_limiter: - $ref: "#/definitions/RateLimiter" - - RateLimiter: - type: object - description: - Defines an IO rate limiter with independent bytes/s and ops/s limits. - Limits are defined by configuring each of the _bandwidth_ and _ops_ token buckets. - properties: - bandwidth: - $ref: "#/definitions/TokenBucket" - description: Token bucket with bytes as tokens - ops: - $ref: "#/definitions/TokenBucket" - description: Token bucket with operations as tokens - - TokenBucket: - type: object - description: - Defines a token bucket with a maximum capacity (size), an initial burst size - (one_time_burst) and an interval for refilling purposes (refill_time). - The refill-rate is derived from size and refill_time, and it is the constant - rate at which the tokens replenish. The refill process only starts happening after - the initial burst budget is consumed. - Consumption from the token bucket is unbounded in speed which allows for bursts - bound in size by the amount of tokens available. - Once the token bucket is empty, consumption speed is bound by the refill_rate. - required: - - size - - refill_time - properties: - size: - type: integer - format: int64 - description: The total number of tokens this bucket can hold. - minimum: 0 - one_time_burst: - type: integer - format: int64 - description: The initial size of a token bucket. - minimum: 0 - refill_time: - type: integer - format: int64 - description: The amount of milliseconds it takes for the bucket to refill. - minimum: 0 - - Vsock: - type: object - required: - - id - - guest_cid - properties: - id: - type: string - guest_cid: - type: integer - minimum: 3 - description: Guest Vsock CID diff --git a/api_server/swagger/firecracker.yaml b/api_server/swagger/firecracker.yaml index 5eba4398dea..0b7b37aec87 100644 --- a/api_server/swagger/firecracker.yaml +++ b/api_server/swagger/firecracker.yaml @@ -2,7 +2,8 @@ swagger: "2.0" info: title: Firecracker API description: RESTful public-facing API. - The API is accessible through HTTP calls on specific URLs carrying JSON modeled data. + The API is accessible through HTTP calls on specific URLs + carrying JSON modeled data. The transport medium is a Unix Domain Socket. version: 0.17.0 termsOfService: "" @@ -352,6 +353,37 @@ paths: schema: $ref: "#/definitions/Error" + /vsocks/{id}: + put: + summary: Creates new vsock with ID specified by the id parameter. + description: + If the vsock device with the specified ID already exists, its body will + be updated based on the new input. May fail if update is not possible. + operationId: putGuestVsockByID + parameters: + - name: id + in: path + description: The id of the vsock device + required: true + type: string + - name: body + in: body + description: Guest vsock properties + required: true + schema: + $ref: "#/definitions/Vsock" + responses: + 204: + description: Vsock created/updated + 400: + description: Vsock cannot be created due to bad input + schema: + $ref: "#/definitions/Error" + default: + description: Internal server error + schema: + $ref: "#/definitions/Error" + definitions: BootSource: type: object @@ -611,3 +643,30 @@ definitions: format: int64 description: The amount of milliseconds it takes for the bucket to refill. minimum: 0 + + Vsock: + type: object + description: + Defines a vsock device, backed by a set of Unix Domain Sockets, on the host side. + For host-initiated connections, Firecracker will be listening on the Unix socket + identified by the path `uds_path`. Firecracker will create this socket, bind and + listen on it. Host-initiated connections will be performed by connection to this + socket and issuing a connection forwarding request to the desired guest-side vsock + port (i.e. `CONNECT 52\n`, to connect to port 52). + For guest-initiated connections, Firecracker will expect host software to be + bound and listening on Unix sockets at `uds_path_`. + E.g. "/path/to/host_vsock.sock_52" for port number 52. + required: + - vsock_id + - guest_cid + - uds_path + properties: + id: + type: string + guest_cid: + type: integer + minimum: 3 + description: Guest Vsock CID + uds_path: + type: string + description: Path to UNIX domain socket, used to proxy vsock connections. diff --git a/devices/Cargo.toml b/devices/Cargo.toml index 6721a0a3545..4f3fa5b8f2d 100644 --- a/devices/Cargo.toml +++ b/devices/Cargo.toml @@ -2,6 +2,7 @@ name = "devices" version = "0.1.0" authors = ["The Chromium OS Authors"] +edition = "2018" [dependencies] byteorder = ">=1.2.1" @@ -17,11 +18,6 @@ net_gen = { path = "../net_gen" } rate_limiter = { path = "../rate_limiter" } sys_util = { path = "../sys_util" } virtio_gen = { path = "../virtio_gen" } -vhost_gen = { path = "../vhost_gen" , optional = true} -vhost_backend = { path = "../vhost_backend", optional = true} [dev-dependencies] tempfile = ">=3.0.2" - -[features] -vsock = ["vhost_gen", "vhost_backend"] diff --git a/devices/src/bus.rs b/devices/src/bus.rs index dc8e145cd21..1ba3778d145 100644 --- a/devices/src/bus.rs +++ b/devices/src/bus.rs @@ -12,7 +12,8 @@ use std::collections::btree_map::BTreeMap; use std::fmt; use std::result; use std::sync::{Arc, Mutex}; -use virtio::AsAny; + +use crate::virtio::AsAny; /// Trait for devices that respond to reads or writes in an arbitrary address space. /// diff --git a/devices/src/legacy/i8042.rs b/devices/src/legacy/i8042.rs index f55bc149e10..b0a71df0e50 100644 --- a/devices/src/legacy/i8042.rs +++ b/devices/src/legacy/i8042.rs @@ -11,7 +11,7 @@ use std::num::Wrapping; use std::{io, result}; use sys_util::EventFd; -use BusDevice; +use crate::bus::BusDevice; #[derive(Debug)] pub enum Error { diff --git a/devices/src/legacy/serial.rs b/devices/src/legacy/serial.rs index 6d48f847793..98efbdac12b 100644 --- a/devices/src/legacy/serial.rs +++ b/devices/src/legacy/serial.rs @@ -11,7 +11,7 @@ use std::io; use logger::{Metric, METRICS}; use sys_util::EventFd; -use BusDevice; +use crate::bus::BusDevice; const LOOP_SIZE: usize = 0x40; diff --git a/devices/src/lib.rs b/devices/src/lib.rs index e92cf0496eb..579554130bd 100644 --- a/devices/src/lib.rs +++ b/devices/src/lib.rs @@ -18,10 +18,6 @@ extern crate net_gen; extern crate net_util; extern crate rate_limiter; extern crate sys_util; -#[cfg(feature = "vsock")] -extern crate vhost_backend; -#[cfg(feature = "vsock")] -extern crate vhost_gen; extern crate virtio_gen; use rate_limiter::Error as RateLimiterError; diff --git a/devices/src/virtio/block.rs b/devices/src/virtio/block.rs index f2632135770..0184fb6f602 100644 --- a/devices/src/virtio/block.rs +++ b/devices/src/virtio/block.rs @@ -16,18 +16,17 @@ use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::mpsc; use std::sync::Arc; -use super::super::Error as DeviceError; -use super::{ - ActivateError, ActivateResult, DescriptorChain, Queue, VirtioDevice, TYPE_BLOCK, - VIRTIO_MMIO_INT_VRING, -}; use logger::{Metric, METRICS}; use memory_model::{GuestAddress, GuestMemory, GuestMemoryError}; use rate_limiter::{RateLimiter, TokenType}; use sys_util::EventFd; -use virtio::EpollConfigConstructor; use virtio_gen::virtio_blk::*; -use {DeviceEventT, EpollHandler}; + +use super::{ + ActivateError, ActivateResult, DescriptorChain, EpollConfigConstructor, Queue, VirtioDevice, + TYPE_BLOCK, VIRTIO_MMIO_INT_VRING, +}; +use crate::{DeviceEventT, EpollHandler, Error as DeviceError}; const CONFIG_SPACE_SIZE: usize = 8; const SECTOR_SHIFT: u8 = 9; @@ -660,7 +659,7 @@ mod tests { use std::time::Duration; use std::u32; - use virtio::queue::tests::*; + use crate::virtio::queue::tests::*; const EPOLLIN: epoll::Events = epoll::Events::EPOLLIN; diff --git a/devices/src/virtio/mmio.rs b/devices/src/virtio/mmio.rs index 0d2031d9100..d21700051e0 100644 --- a/devices/src/virtio/mmio.rs +++ b/devices/src/virtio/mmio.rs @@ -10,10 +10,11 @@ use std::sync::Arc; use byteorder::{ByteOrder, LittleEndian}; -use super::*; use memory_model::{GuestAddress, GuestMemory}; use sys_util::EventFd; -use BusDevice; + +use super::*; +use crate::bus::BusDevice; //TODO crosvm uses 0 here, but IIRC virtio specified some other vendor id that should be used const VENDOR_ID: u32 = 0; diff --git a/devices/src/virtio/mod.rs b/devices/src/virtio/mod.rs index 133e28c66fe..62d83041985 100644 --- a/devices/src/virtio/mod.rs +++ b/devices/src/virtio/mod.rs @@ -16,15 +16,13 @@ pub mod block; mod mmio; pub mod net; mod queue; -#[cfg(feature = "vsock")] -pub mod vhost; +pub mod vsock; pub use self::block::*; pub use self::mmio::*; pub use self::net::*; pub use self::queue::*; -#[cfg(feature = "vsock")] -pub use self::vhost::vsock::*; +pub use self::vsock::*; use super::EpollHandler; @@ -53,8 +51,6 @@ pub const NOTIFY_REG_OFFSET: u32 = 0x50; pub enum ActivateError { EpollCtl(IOError), BadActivate, - #[cfg(feature = "vsock")] - BadVhostActivate(self::vhost::Error), } pub type ActivateResult = std::result::Result<(), ActivateError>; diff --git a/devices/src/virtio/net.rs b/devices/src/virtio/net.rs index 87393858e2c..5d5f1a55182 100644 --- a/devices/src/virtio/net.rs +++ b/devices/src/virtio/net.rs @@ -20,8 +20,6 @@ use std::sync::mpsc; use std::sync::Arc; use std::vec::Vec; -use super::super::Error as DeviceError; -use super::{ActivateError, ActivateResult, Queue, VirtioDevice, TYPE_NET, VIRTIO_MMIO_INT_VRING}; use dumbo::{ns::MmdsNetworkStack, pdu::ethernet::EthernetFrame}; use logger::{Metric, METRICS}; use memory_model::{GuestAddress, GuestMemory}; @@ -29,9 +27,13 @@ use net_gen; use net_util::{MacAddr, Tap, TapError, MAC_ADDR_LEN}; use rate_limiter::{RateLimiter, TokenBucket, TokenType}; use sys_util::EventFd; -use virtio::EpollConfigConstructor; use virtio_gen::virtio_net::*; -use {DeviceEventT, EpollHandler}; + +use super::{ + ActivateError, ActivateResult, EpollConfigConstructor, Queue, VirtioDevice, TYPE_NET, + VIRTIO_MMIO_INT_VRING, +}; +use crate::{DeviceEventT, EpollHandler, Error as DeviceError}; /// The maximum buffer size when segmentation offload is enabled. This /// includes the 12-byte virtio net header. @@ -973,13 +975,13 @@ mod tests { use libc; - use super::*; - use memory_model::GuestAddress; - use virtio::queue::tests::*; - use dumbo::pdu::{arp, ethernet}; + use memory_model::GuestAddress; use rate_limiter::TokenBucket; + use super::*; + use crate::virtio::queue::tests::*; + const EPOLLIN: epoll::Events = epoll::Events::EPOLLIN; /// Will read $metric, run the code in $block, then assert metric has increased by $delta. diff --git a/devices/src/virtio/queue.rs b/devices/src/virtio/queue.rs index c113ba4e5ab..5bbc53c26d9 100644 --- a/devices/src/virtio/queue.rs +++ b/devices/src/virtio/queue.rs @@ -36,11 +36,13 @@ unsafe impl DataInit for Descriptor {} /// A virtio descriptor chain. pub struct DescriptorChain<'a> { - mem: &'a GuestMemory, desc_table: GuestAddress, queue_size: u16, ttl: u16, // used to prevent infinite chain cycles + /// Reference to guest memory + pub mem: &'a GuestMemory, + /// Index into the descriptor table pub index: u16, diff --git a/devices/src/virtio/vhost/handle.rs b/devices/src/virtio/vhost/handle.rs deleted file mode 100644 index 505c9da09b8..00000000000 --- a/devices/src/virtio/vhost/handle.rs +++ /dev/null @@ -1,144 +0,0 @@ -// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 -// -// Portions 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 THIRD-PARTY file. - -use super::INTERRUPT_STATUS_USED_RING; - -use sys_util::EventFd; -use vhost_backend::Vhost; -use virtio::EpollConfigConstructor; -use DeviceEventT; -use EpollHandler; - -use super::super::super::Error as DeviceError; -use std::os::unix::io::{AsRawFd, RawFd}; -use std::sync::atomic::{AtomicUsize, Ordering}; -use std::sync::mpsc; -use std::sync::Arc; - -/// Event for injecting IRQ into guest. -pub const VHOST_IRQ_AVAILABLE: DeviceEventT = 0; -/// Event for stopping the vhost device. -pub const KILL_EVENT: DeviceEventT = 1; -// VHOST_IRQ_AVAILABLE and KILL_EVENT. KILL_EVENT is unused yet. -pub const VHOST_EVENTS_COUNT: usize = 2; - -pub struct VhostEpollHandler { - vhost_dev: T, - interrupt_status: Arc, - interrupt_evt: EventFd, - queue_evt: EventFd, -} - -impl VhostEpollHandler { - /// Construct a new, empty event handler for vhost-based devices. - /// - /// # Arguments - /// * `vhost_dev` - the vhost-based device info - /// * `interrupt_status` - semaphore before triggering interrupt event - /// * `interrupt_evt` EventFd for signaling an MMIO interrupt that the guest - /// driver is listening to - /// * `queue_evt` - EventFd used by the handle to monitor queue events - pub fn new( - vhost_dev: T, - interrupt_status: Arc, - interrupt_evt: EventFd, - queue_evt: EventFd, - ) -> VhostEpollHandler { - VhostEpollHandler { - vhost_dev, - interrupt_status, - interrupt_evt, - queue_evt, - } - } - - fn signal_used_queue(&self) -> std::result::Result<(), DeviceError> { - self.interrupt_status - .fetch_or(INTERRUPT_STATUS_USED_RING as usize, Ordering::SeqCst); - self.interrupt_evt - .write(1) - .map_err(DeviceError::FailedSignalingUsedQueue) - } - - pub fn get_queue_evt(&self) -> RawFd { - self.queue_evt.as_raw_fd() - } - - pub fn get_device(&self) -> &T { - &self.vhost_dev - } -} - -impl EpollHandler for VhostEpollHandler -where - T: std::marker::Send, -{ - fn handle_event( - &mut self, - device_event: DeviceEventT, - _evset: epoll::Events, - ) -> std::result::Result<(), DeviceError> { - match device_event { - VHOST_IRQ_AVAILABLE => { - if let Err(e) = self.queue_evt.read() { - error!("failed reading queue EventFd: {:?}", e); - Err(DeviceError::FailedReadingQueue { - event_type: "EventFd", - underlying: e, - }) - } else { - self.signal_used_queue() - } - } - KILL_EVENT => { - //TODO: call API for device removal here - info!("vhost device removed"); - Ok(()) - } - other => Err(DeviceError::UnknownEvent { - device: "VhostEpollHandler", - event: other, - }), - } - } -} - -pub struct VhostEpollConfig { - queue_evt_token: u64, - kill_token: u64, - epoll_raw_fd: RawFd, - sender: mpsc::Sender>, -} - -impl VhostEpollConfig { - pub fn get_sender(&self) -> mpsc::Sender> { - self.sender.clone() - } - - pub fn get_raw_epoll_fd(&self) -> RawFd { - self.epoll_raw_fd - } - - pub fn get_kill_token(&self) -> u64 { - self.kill_token - } - - pub fn get_queue_evt_token(&self) -> u64 { - self.queue_evt_token - } -} - -impl EpollConfigConstructor for VhostEpollConfig { - fn new(first_token: u64, epoll_raw_fd: RawFd, sender: mpsc::Sender>) -> Self { - VhostEpollConfig { - queue_evt_token: first_token, - kill_token: first_token + 1, - epoll_raw_fd, - sender, - } - } -} diff --git a/devices/src/virtio/vhost/mod.rs b/devices/src/virtio/vhost/mod.rs deleted file mode 100644 index 738526e4e5d..00000000000 --- a/devices/src/virtio/vhost/mod.rs +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 -// -// Portions 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 THIRD-PARTY file. - -//! Implements vhost-based virtio devices. - -use std; -use std::io; - -pub mod handle; -pub mod vsock; - -#[derive(Debug)] -pub enum Error { - /// Creating kill eventfd failed. - CreateKillEventFd(io::Error), - /// Cloning kill eventfd failed. - CloneKillEventFd(io::Error), - /// Error while polling for events. - PollError(io::Error), - /// Failed to open vhost device. - VhostOpen(vhost_backend::Error), - /// Set owner failed. - VhostSetOwner(vhost_backend::Error), - /// Get features failed. - VhostGetFeatures(vhost_backend::Error), - /// Set features failed. - VhostSetFeatures(vhost_backend::Error), - /// Set mem table failed. - VhostSetMemTable(vhost_backend::Error), - /// Set vring num failed. - VhostSetVringNum(vhost_backend::Error), - /// Set vring addr failed. - VhostSetVringAddr(vhost_backend::Error), - /// Set vring base failed. - VhostSetVringBase(vhost_backend::Error), - /// Set vring call failed. - VhostSetVringCall(vhost_backend::Error), - /// Set vring kick failed. - VhostSetVringKick(vhost_backend::Error), - /// Net set backend failed. - VhostNetSetBackend(vhost_backend::Error), - /// Failed to set CID for guest. - VhostVsockSetCid(vhost_backend::Error), - /// Failed to start vhost-vsock driver. - VhostVsockStart(vhost_backend::Error), - /// Failed to create vhost eventfd. - VhostIrqCreate(io::Error), - /// Failed to read vhost eventfd. - VhostIrqRead(io::Error), -} -type Result = std::result::Result; -const INTERRUPT_STATUS_USED_RING: u32 = 0x1; -pub const TYPE_VSOCK: u32 = 19; diff --git a/devices/src/virtio/vhost/vsock.rs b/devices/src/virtio/vhost/vsock.rs deleted file mode 100644 index 90e50eadc66..00000000000 --- a/devices/src/virtio/vhost/vsock.rs +++ /dev/null @@ -1,233 +0,0 @@ -// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 -// -// Portions 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 THIRD-PARTY file. - -use super::super::{ActivateError, ActivateResult, Queue, VirtioDevice}; -use super::handle::*; -use super::*; - -use memory_model::GuestMemory; -use sys_util::EventFd; -use vhost_backend::Vhost; -use vhost_backend::Vsock as VhostVsockFd; - -use byteorder::{ByteOrder, LittleEndian}; -use epoll; -use std::sync::atomic::AtomicUsize; -use std::sync::Arc; - -const QUEUE_SIZE: u16 = 256; -const NUM_QUEUES: usize = 3; -const QUEUE_SIZES: &[u16] = &[QUEUE_SIZE; NUM_QUEUES]; - -impl std::convert::From for ActivateError { - fn from(error: super::Error) -> Self { - ActivateError::BadVhostActivate(error) - } -} - -pub struct Vsock { - vsock_fd: Option, - cid: u64, - avail_features: u64, - acked_features: u64, - config_space: Vec, - epoll_config: VhostEpollConfig, - interrupt: Option, -} - -impl Vsock { - /// Create a new virtio-vsock device with the given VM cid. - pub fn new(cid: u64, mem: &GuestMemory, epoll_config: VhostEpollConfig) -> Result { - let fd = VhostVsockFd::new(mem).map_err(Error::VhostOpen)?; - let avail_features = fd.get_features().map_err(Error::VhostGetFeatures)?; - - Ok(Vsock { - vsock_fd: Some(fd), - cid, - avail_features, - acked_features: 0, - config_space: Vec::new(), - epoll_config, - interrupt: Some(EventFd::new().map_err(Error::VhostIrqCreate)?), - }) - } -} - -impl VirtioDevice for Vsock { - fn device_type(&self) -> u32 { - TYPE_VSOCK - } - - 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!( - "vsock: virtio-vsock got request for features page: {}", - 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!( - "vsock: virtio-vsock 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!("vsock: virtio-vsock got unknown feature ack: {:x}", v); - - // Don't count these features as acked. - v &= !unrequested_features; - } - self.acked_features |= v; - } - - fn read_config(&self, offset: u64, data: &mut [u8]) { - match offset { - 0 if data.len() == 8 => LittleEndian::write_u64(data, self.cid), - 0 if data.len() == 4 => LittleEndian::write_u32(data, (self.cid & 0xffff_ffff) as u32), - 4 if data.len() == 4 => { - LittleEndian::write_u32(data, ((self.cid >> 32) & 0xffff_ffff) as u32) - } - _ => warn!( - "vsock: virtio-vsock received invalid read request of {} bytes at offset {}", - data.len(), - offset - ), - } - } - - fn write_config(&mut self, offset: u64, data: &[u8]) { - let data_len = data.len() as u64; - let config_len = self.config_space.len() as u64; - if offset + data_len > config_len { - error!("Failed to write config space"); - return; - } - let (_, right) = self.config_space.split_at_mut(offset as usize); - right.copy_from_slice(&data[..]); - } - - fn activate( - &mut self, - _: GuestMemory, - interrupt_evt: EventFd, - interrupt_status: Arc, - queues: Vec, - 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); - } - - if let Some(vsock_fd) = self.vsock_fd.take() { - if let Some(interrupt) = self.interrupt.take() { - let cid = self.cid; - - // The third vq is an event-only vq that is not handled by the vhost - // subsystem (but still needs to exist). Split it off here. - let vhost_queues = queues[..2].to_vec(); - - // Preliminary setup for vhost net. - vsock_fd.set_owner().map_err(Error::VhostSetOwner)?; - - vsock_fd - .set_features(self.acked_features) - .map_err(Error::VhostSetFeatures)?; - - vsock_fd.set_mem_table().map_err(Error::VhostSetMemTable)?; - - for (queue_index, ref queue) in vhost_queues.iter().enumerate() { - vsock_fd - .set_vring_num(queue_index, queue.get_max_size()) - .map_err(Error::VhostSetVringNum)?; - - vsock_fd - .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(Error::VhostSetVringAddr)?; - vsock_fd - .set_vring_base(queue_index, 0) - .map_err(Error::VhostSetVringBase)?; - vsock_fd - .set_vring_call(queue_index, &interrupt) - .map_err(Error::VhostSetVringCall)?; - vsock_fd - .set_vring_kick(queue_index, &queue_evts[queue_index]) - .map_err(Error::VhostSetVringKick)?; - } - - let handler = - VhostEpollHandler::new(vsock_fd, interrupt_status, interrupt_evt, interrupt); - - // vsock specific ioctl setup for running device. - handler - .get_device() - .set_guest_cid(cid) - .map_err(Error::VhostVsockSetCid)?; - handler - .get_device() - .start() - .map_err(Error::VhostVsockStart)?; - - let queue_evt_raw_fd = handler.get_queue_evt(); - //channel should be open and working - self.epoll_config - .get_sender() - .send(Box::new(handler)) - .unwrap(); - - epoll::ctl( - self.epoll_config.get_raw_epoll_fd(), - epoll::ControlOptions::EPOLL_CTL_ADD, - queue_evt_raw_fd, - epoll::Event::new( - epoll::Events::EPOLLIN, - self.epoll_config.get_queue_evt_token(), - ), - ) - .map_err(ActivateError::EpollCtl)?; - - return Ok(()); - } - } - Err(ActivateError::BadActivate) - } -} diff --git a/devices/src/virtio/vsock/device.rs b/devices/src/virtio/vsock/device.rs new file mode 100644 index 00000000000..d65f041ecb2 --- /dev/null +++ b/devices/src/virtio/vsock/device.rs @@ -0,0 +1,326 @@ +// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +// +// Portions 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 THIRD-PARTY file. + +/// This is the `VirtioDevice` implementation for our vsock device. It handles the virtio-level +/// device logic: feature negociation, device configuration, and device activation. +/// The run-time device logic (i.e. event-driven data handling) is implemented by +/// `super::epoll_handler::EpollHandler`. +/// +/// We aim to conform to the VirtIO v1.1 spec: +/// https://docs.oasis-open.org/virtio/virtio/v1.1/virtio-v1.1.html +/// +/// The vsock device has two input parameters: a CID to identify the device, and a `VsockBackend` +/// to use for offloading vsock traffic. +/// +/// Upon its activation, the vsock device creates its `EpollHandler`, passes it the event-interested +/// file descriptors, and registers these descriptors with the VMM `EpollContext`. Going forward, +/// the `EpollHandler` will get notified whenever an event occurs on the just-registered FDs: +/// - an RX queue FD; +/// - a TX queue FD; +/// - an event queue FD; and +/// - a backend FD. +/// +use std::os::unix::io::AsRawFd; +use std::sync::atomic::AtomicUsize; +use std::sync::Arc; + +use byteorder::{ByteOrder, LittleEndian}; + +use memory_model::GuestMemory; +use sys_util::EventFd; + +use super::super::{ActivateError, ActivateResult, Queue as VirtQueue, VirtioDevice}; +use super::epoll_handler::VsockEpollHandler; +use super::VsockBackend; +use super::{defs, defs::uapi, EpollConfig}; + +/// The virtio features supported by our vsock device: +/// - VIRTIO_F_VERSION_1: the device conforms to at least version 1.0 of the VirtIO spec. +/// - VIRTIO_F_IN_ORDER: the device returns used buffers in the same order that the driver makes +/// them available. +const AVAIL_FEATURES: u64 = + 1 << uapi::VIRTIO_F_VERSION_1 as u64 | 1 << uapi::VIRTIO_F_IN_ORDER as u64; + +pub struct Vsock { + cid: u64, + backend: Option, + avail_features: u64, + acked_features: u64, + epoll_config: EpollConfig, +} + +impl Vsock +where + B: VsockBackend, +{ + /// Create a new virtio-vsock device with the given VM CID and vsock backend. + pub fn new(cid: u64, epoll_config: EpollConfig, backend: B) -> super::Result> { + Ok(Vsock { + cid, + avail_features: AVAIL_FEATURES, + acked_features: 0, + epoll_config, + backend: Some(backend), + }) + } +} + +impl VirtioDevice for Vsock +where + B: VsockBackend + 'static, +{ + fn device_type(&self) -> u32 { + uapi::VIRTIO_ID_VSOCK + } + + fn queue_max_sizes(&self) -> &[u16] { + defs::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: {}", 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: {}", 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: {:x}", v); + + // Don't count these features as acked. + v &= !unrequested_features; + } + self.acked_features |= v; + } + + fn read_config(&self, offset: u64, data: &mut [u8]) { + match offset { + 0 if data.len() == 8 => LittleEndian::write_u64(data, self.cid), + 0 if data.len() == 4 => LittleEndian::write_u32(data, (self.cid & 0xffff_ffff) as u32), + 4 if data.len() == 4 => { + LittleEndian::write_u32(data, ((self.cid >> 32) & 0xffff_ffff) as u32) + } + _ => warn!( + "vsock: virtio-vsock received invalid read request of {} bytes at offset {}", + data.len(), + offset + ), + } + } + + fn write_config(&mut self, offset: u64, data: &[u8]) { + warn!( + "vsock: guest driver attempted to write device config (offset={:x}, len={:x})", + offset, + data.len() + ); + } + + fn activate( + &mut self, + mem: GuestMemory, + interrupt_evt: EventFd, + interrupt_status: Arc, + mut queues: Vec, + mut queue_evts: Vec, + ) -> ActivateResult { + if queues.len() != defs::NUM_QUEUES || queue_evts.len() != defs::NUM_QUEUES { + error!( + "Cannot perform activate. Expected {} queue(s), got {}", + defs::NUM_QUEUES, + queues.len() + ); + return Err(ActivateError::BadActivate); + } + + let rxvq = queues.remove(0); + let txvq = queues.remove(0); + let evvq = queues.remove(0); + + let rxvq_evt = queue_evts.remove(0); + let txvq_evt = queue_evts.remove(0); + let evvq_evt = queue_evts.remove(0); + + let backend = self.backend.take().unwrap(); + let backend_fd = backend.get_polled_fd(); + let backend_evset = backend.get_polled_evset(); + + let handler: VsockEpollHandler = VsockEpollHandler { + rxvq, + rxvq_evt, + txvq, + txvq_evt, + evvq, + evvq_evt, + mem, + cid: self.cid, + interrupt_status, + interrupt_evt, + backend, + }; + let rx_queue_rawfd = handler.rxvq_evt.as_raw_fd(); + let tx_queue_rawfd = handler.txvq_evt.as_raw_fd(); + let ev_queue_rawfd = handler.evvq_evt.as_raw_fd(); + + self.epoll_config + .sender + .send(Box::new(handler)) + .expect("Failed to send handler through channel"); + + epoll::ctl( + self.epoll_config.epoll_raw_fd, + epoll::ControlOptions::EPOLL_CTL_ADD, + rx_queue_rawfd, + epoll::Event::new(epoll::Events::EPOLLIN, self.epoll_config.rxq_token), + ) + .map_err(ActivateError::EpollCtl)?; + + epoll::ctl( + self.epoll_config.epoll_raw_fd, + epoll::ControlOptions::EPOLL_CTL_ADD, + tx_queue_rawfd, + epoll::Event::new(epoll::Events::EPOLLIN, self.epoll_config.txq_token), + ) + .map_err(ActivateError::EpollCtl)?; + + epoll::ctl( + self.epoll_config.epoll_raw_fd, + epoll::ControlOptions::EPOLL_CTL_ADD, + ev_queue_rawfd, + epoll::Event::new(epoll::Events::EPOLLIN, self.epoll_config.evq_token), + ) + .map_err(ActivateError::EpollCtl)?; + + epoll::ctl( + self.epoll_config.epoll_raw_fd, + epoll::ControlOptions::EPOLL_CTL_ADD, + backend_fd, + epoll::Event::new(backend_evset, self.epoll_config.backend_token), + ) + .map_err(ActivateError::EpollCtl)?; + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use crate::virtio::vsock::defs::uapi; + + use super::super::tests::TestContext; + use super::*; + + #[test] + fn test_virtio_device() { + let mut ctx = TestContext::new(); + let device_features = AVAIL_FEATURES; + let driver_features: u64 = AVAIL_FEATURES | 1 | (1 << 32); + let device_pages = [ + (device_features & 0xffff_ffff) as u32, + (device_features >> 32) as u32, + ]; + let driver_pages = [ + (driver_features & 0xffff_ffff) as u32, + (driver_features >> 32) as u32, + ]; + assert_eq!(ctx.device.device_type(), uapi::VIRTIO_ID_VSOCK); + assert_eq!(ctx.device.queue_max_sizes(), defs::QUEUE_SIZES); + assert_eq!(ctx.device.features(0), device_pages[0]); + assert_eq!(ctx.device.features(1), device_pages[1]); + assert_eq!(ctx.device.features(2), 0); + + // Ack device features, page 0. + ctx.device.ack_features(0, driver_pages[0]); + // Ack device features, page 1. + ctx.device.ack_features(1, driver_pages[1]); + // Ack some bogus page (i.e. 2). This should have no side effect. + ctx.device.ack_features(2, 0); + // Attempt to un-ack the first feature page. This should have no side effect. + ctx.device.ack_features(0, !driver_pages[0]); + // Check that no side effect are present, and that the acked features are exactly the same + // as the device features. + assert_eq!(ctx.device.acked_features, device_features & driver_features); + + // Test reading 32-bit chunks. + let mut data = [0u8; 8]; + ctx.device.read_config(0, &mut data[..4]); + assert_eq!( + u64::from(LittleEndian::read_u32(&data)), + ctx.cid & 0xffff_ffff + ); + ctx.device.read_config(4, &mut data[4..]); + assert_eq!( + u64::from(LittleEndian::read_u32(&data[4..])), + (ctx.cid >> 32) & 0xffff_ffff + ); + + // Test reading 64-bit. + let mut data = [0u8; 8]; + ctx.device.read_config(0, &mut data); + assert_eq!(LittleEndian::read_u64(&data), ctx.cid); + + // Check that out-of-bounds reading doesn't mutate the destination buffer. + let mut data = [0u8, 1, 2, 3, 4, 5, 6, 7]; + ctx.device.read_config(2, &mut data); + assert_eq!(data, [0u8, 1, 2, 3, 4, 5, 6, 7]); + + // Just covering lines here, since the vsock device has no writable config. + // A warning is, however, logged, if the guest driver attempts to write any config data. + ctx.device.write_config(0, &data[..4]); + + // Test a bad activation. + let bad_activate = ctx.device.activate( + ctx.mem.clone(), + EventFd::new().unwrap(), + Arc::new(AtomicUsize::new(0)), + Vec::new(), + Vec::new(), + ); + match bad_activate { + Err(ActivateError::BadActivate) => (), + other => panic!("{:?}", other), + } + + // Test a correct activation. + ctx.device + .activate( + ctx.mem.clone(), + EventFd::new().unwrap(), + Arc::new(AtomicUsize::new(0)), + vec![ + VirtQueue::new(256), + VirtQueue::new(256), + VirtQueue::new(256), + ], + vec![ + EventFd::new().unwrap(), + EventFd::new().unwrap(), + EventFd::new().unwrap(), + ], + ) + .unwrap(); + } + +} diff --git a/devices/src/virtio/vsock/epoll_handler.rs b/devices/src/virtio/vsock/epoll_handler.rs new file mode 100644 index 00000000000..0d987e101a3 --- /dev/null +++ b/devices/src/virtio/vsock/epoll_handler.rs @@ -0,0 +1,463 @@ +// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +// +// Portions 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 THIRD-PARTY file. + +/// The vsock `EpollHandler` implements the runtime logic of our vsock device: +/// 1. Respond to TX queue events by wrapping virtio buffers into `VsockPacket`s, then sending those +/// packets to the `VsockBackend`; +/// 2. Forward backend FD event notifications to the `VsockBackend`; +/// 3. Fetch incoming packets from the `VsockBackend` and place them into the virtio RX queue; +/// 4. Whenever we have processed some virtio buffers (either TX or RX), let the driver know by +/// raising our assigned IRQ. +/// +/// In a nutshell, the `EpollHandler` logic looks like this: +/// - on TX queue event: +/// - fetch all packets from the TX queue and send them to the backend; then +/// - if the backend has queued up any incoming packets, fetch them into any available RX buffers. +/// - on RX queue event: +/// - fetch any incoming packets, queued up by the backend, into newly available RX buffers. +/// - on backend event: +/// - forward the event to the backend; then +/// - again, attempt to fetch any incoming packets queued by the backend into virtio RX buffers. +/// +use std::result; +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::Arc; + +use memory_model::GuestMemory; +use sys_util::EventFd; + +use super::super::super::{DeviceEventT, Error as DeviceError}; +use super::super::queue::Queue as VirtQueue; +use super::super::VIRTIO_MMIO_INT_VRING; +use super::defs; +use super::packet::VsockPacket; +use super::{EpollHandler, VsockBackend}; + +// TODO: Detect / handle queue deadlock: +// 1. If the driver halts RX queue processing, we'll need to notify `self.backend`, so that it +// can unregister any EPOLLIN listeners, since otherwise it will keep spinning, unable to consume +// its EPOLLIN events. + +pub struct VsockEpollHandler { + pub rxvq: VirtQueue, + pub rxvq_evt: EventFd, + pub txvq: VirtQueue, + pub txvq_evt: EventFd, + pub evvq: VirtQueue, + pub evvq_evt: EventFd, + pub cid: u64, + pub mem: GuestMemory, + pub interrupt_status: Arc, + pub interrupt_evt: EventFd, + pub backend: B, +} + +impl VsockEpollHandler +where + B: VsockBackend + 'static, +{ + /// Signal the guest driver that we've used some virtio buffers that it had previously made + /// available. + /// + fn signal_used_queue(&self) -> result::Result<(), DeviceError> { + debug!("vsock: raising IRQ"); + self.interrupt_status + .fetch_or(VIRTIO_MMIO_INT_VRING as usize, Ordering::SeqCst); + self.interrupt_evt.write(1).map_err(|e| { + error!("Failed to signal used queue: {:?}", e); + DeviceError::FailedSignalingUsedQueue(e) + }) + } + + /// Walk the driver-provided RX queue buffers and attempt to fill them up with any data that we + /// have pending. + /// + fn process_rx(&mut self) -> bool { + debug!("vsock: epoll_handler::process_rx()"); + + let mut have_used = false; + + while let Some(head) = self.rxvq.pop(&self.mem) { + let used_len = match VsockPacket::from_rx_virtq_head(&head) { + Ok(mut pkt) => { + if self.backend.recv_pkt(&mut pkt).is_ok() { + pkt.hdr().len() as u32 + pkt.len() + } else { + // We are using a consuming iterator over the virtio buffers, so, if we can't + // fill in this buffer, we'll need to undo the last iterator step. + self.rxvq.undo_pop(); + break; + } + } + Err(e) => { + warn!("vsock: RX queue error: {:?}", e); + 0 + } + }; + + have_used = true; + self.rxvq.add_used(&self.mem, head.index, used_len); + } + + have_used + } + + /// Walk the driver-provided TX queue buffers, package them up as vsock packets, and send them to + /// the backend for processing. + /// + fn process_tx(&mut self) -> bool { + debug!("vsock: epoll_handler::process_tx()"); + + let mut have_used = false; + + while let Some(head) = self.txvq.pop(&self.mem) { + let pkt = match VsockPacket::from_tx_virtq_head(&head) { + Ok(pkt) => pkt, + Err(e) => { + error!("vsock: error reading TX packet: {:?}", e); + have_used = true; + self.txvq.add_used(&self.mem, head.index, 0); + continue; + } + }; + + if self.backend.send_pkt(&pkt).is_err() { + self.txvq.undo_pop(); + break; + } + + have_used = true; + self.txvq.add_used(&self.mem, head.index, 0); + } + + have_used + } +} + +impl EpollHandler for VsockEpollHandler +where + B: VsockBackend, +{ + /// Respond to a new event, coming from the main epoll loop (implemented by the VMM). + /// + fn handle_event( + &mut self, + device_event: DeviceEventT, + evset: epoll::Events, + ) -> result::Result<(), DeviceError> { + let mut raise_irq = false; + + match device_event { + defs::RXQ_EVENT => { + debug!("vsock: RX queue event"); + if let Err(e) = self.rxvq_evt.read() { + error!("Failed to get rx queue event: {:?}", e); + return Err(DeviceError::FailedReadingQueue { + event_type: "rx queue event", + underlying: e, + }); + } else if self.backend.has_pending_rx() { + raise_irq |= self.process_rx(); + } + } + defs::TXQ_EVENT => { + debug!("vsock: TX queue event"); + if let Err(e) = self.txvq_evt.read() { + error!("Failed to get tx queue event: {:?}", e); + return Err(DeviceError::FailedReadingQueue { + event_type: "tx queue event", + underlying: e, + }); + } else { + raise_irq |= self.process_tx(); + // The backend may have queued up responses to the packets we sent during TX queue + // processing. If that happened, we need to fetch those responses and place them + // into RX buffers. + if self.backend.has_pending_rx() { + raise_irq |= self.process_rx(); + } + } + } + defs::EVQ_EVENT => { + debug!("vsock: event queue event"); + if let Err(e) = self.evvq_evt.read() { + error!("Failed to consume evq event: {:?}", e); + return Err(DeviceError::FailedReadingQueue { + event_type: "ev queue event", + underlying: e, + }); + } + } + defs::BACKEND_EVENT => { + debug!("vsock: backend event"); + self.backend.notify(evset); + // After the backend has been kicked, it might've freed up some resources, so we + // can attempt to send it more data to process. + // In particular, if `self.backend.send_pkt()` halted the TX queue processing (by + // reurning an error) at some point in the past, now is the time to try walking the + // TX queue again. + raise_irq |= self.process_tx(); + if self.backend.has_pending_rx() { + raise_irq |= self.process_rx(); + } + } + other => { + return Err(DeviceError::UnknownEvent { + device: "vsock", + event: other, + }); + } + } + + if raise_irq { + self.signal_used_queue().unwrap_or_default(); + } + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::super::tests::TestContext; + use super::super::*; + use super::*; + use crate::virtio::vsock::defs::{BACKEND_EVENT, EVQ_EVENT, RXQ_EVENT, TXQ_EVENT}; + + #[test] + fn test_irq() { + // Test case: successful IRQ signaling. + { + let test_ctx = TestContext::new(); + let ctx = test_ctx.create_epoll_handler_context(); + + ctx.handler.signal_used_queue().unwrap(); + assert_eq!( + ctx.handler.interrupt_status.load(Ordering::SeqCst), + VIRTIO_MMIO_INT_VRING as usize + ); + assert_eq!(ctx.handler.interrupt_evt.read().unwrap(), 1); + } + + // Test case: error (a real stretch) - the event counter is full. + // + { + let test_ctx = TestContext::new(); + let ctx = test_ctx.create_epoll_handler_context(); + + ctx.handler.interrupt_evt.write(std::u64::MAX - 1).unwrap(); + match ctx.handler.signal_used_queue() { + Err(DeviceError::FailedSignalingUsedQueue(_)) => (), + other => panic!("{:?}", other), + } + } + } + + #[test] + fn test_txq_event() { + // Test case: + // - the driver has something to send (there's data in the TX queue); and + // - the backend has no pending RX data. + { + let test_ctx = TestContext::new(); + let mut ctx = test_ctx.create_epoll_handler_context(); + + ctx.handler.backend.set_pending_rx(false); + ctx.signal_txq_event(); + + // The available TX descriptor should have been used. + assert_eq!(ctx.guest_txvq.used.idx.get(), 1); + // The available RX descriptor should be untouched. + assert_eq!(ctx.guest_rxvq.used.idx.get(), 0); + } + + // Test case: + // - the driver has something to send (there's data in the TX queue); and + // - the backend also has some pending RX data. + { + let test_ctx = TestContext::new(); + let mut ctx = test_ctx.create_epoll_handler_context(); + + ctx.handler.backend.set_pending_rx(true); + ctx.signal_txq_event(); + + // Both available RX and TX descriptors should have been used. + assert_eq!(ctx.guest_txvq.used.idx.get(), 1); + assert_eq!(ctx.guest_rxvq.used.idx.get(), 1); + } + + // Test case: + // - the driver has something to send (there's data in the TX queue); and + // - the backend errors out and cannot process the TX queue. + { + let test_ctx = TestContext::new(); + let mut ctx = test_ctx.create_epoll_handler_context(); + + ctx.handler.backend.set_pending_rx(false); + ctx.handler.backend.set_tx_err(Some(VsockError::NoData)); + ctx.signal_txq_event(); + + // Both RX and TX queues should be untouched. + assert_eq!(ctx.guest_txvq.used.idx.get(), 0); + assert_eq!(ctx.guest_rxvq.used.idx.get(), 0); + } + + // Test case: + // - the driver supplied a malformed TX buffer. + { + let test_ctx = TestContext::new(); + let mut ctx = test_ctx.create_epoll_handler_context(); + + // Invalidate the packet header descriptor, by setting its length to 0. + ctx.guest_txvq.dtable[0].len.set(0); + ctx.signal_txq_event(); + + // The available descriptor should have been consumed, but no packet should have + // reached the backend. + assert_eq!(ctx.guest_txvq.used.idx.get(), 1); + assert_eq!(ctx.handler.backend.tx_ok_cnt, 0); + } + + // Test case: spurious TXQ_EVENT. + { + let test_ctx = TestContext::new(); + let mut ctx = test_ctx.create_epoll_handler_context(); + + match ctx.handler.handle_event(TXQ_EVENT, epoll::Events::EPOLLIN) { + Err(DeviceError::FailedReadingQueue { .. }) => (), + other => panic!("{:?}", other), + } + } + } + + #[test] + fn test_rxq_event() { + // Test case: + // - there is pending RX data in the backend; and + // - the driver makes RX buffers available; and + // - the backend successfully places its RX data into the queue. + { + let test_ctx = TestContext::new(); + let mut ctx = test_ctx.create_epoll_handler_context(); + + ctx.handler.backend.set_pending_rx(true); + ctx.handler.backend.set_rx_err(Some(VsockError::NoData)); + ctx.signal_rxq_event(); + + // The available RX buffer should've been left untouched. + assert_eq!(ctx.guest_rxvq.used.idx.get(), 0); + } + + // Test case: + // - there is pending RX data in the backend; and + // - the driver makes RX buffers available; and + // - the backend errors out, when attempting to receive data. + { + let test_ctx = TestContext::new(); + let mut ctx = test_ctx.create_epoll_handler_context(); + + ctx.handler.backend.set_pending_rx(true); + ctx.signal_rxq_event(); + + // The available RX buffer should have been used. + assert_eq!(ctx.guest_rxvq.used.idx.get(), 1); + } + + // Test case: the driver provided a malformed RX descriptor chain. + { + let test_ctx = TestContext::new(); + let mut ctx = test_ctx.create_epoll_handler_context(); + + // Invalidate the packet header descriptor, by setting its length to 0. + ctx.guest_rxvq.dtable[0].len.set(0); + + // The chain should've been processed, without employing the backend. + assert_eq!(ctx.handler.process_rx(), true); + assert_eq!(ctx.guest_rxvq.used.idx.get(), 1); + assert_eq!(ctx.handler.backend.rx_ok_cnt, 0); + } + + // Test case: spurious RXQ_EVENT. + { + let test_ctx = TestContext::new(); + let mut ctx = test_ctx.create_epoll_handler_context(); + ctx.handler.backend.set_pending_rx(false); + match ctx.handler.handle_event(RXQ_EVENT, epoll::Events::EPOLLIN) { + Err(DeviceError::FailedReadingQueue { .. }) => (), + other => panic!("{:?}", other), + } + } + } + + #[test] + fn test_evq_event() { + // Test case: spurious EVQ_EVENT. + { + let test_ctx = TestContext::new(); + let mut ctx = test_ctx.create_epoll_handler_context(); + ctx.handler.backend.set_pending_rx(false); + match ctx.handler.handle_event(EVQ_EVENT, epoll::Events::EPOLLIN) { + Err(DeviceError::FailedReadingQueue { .. }) => (), + other => panic!("{:?}", other), + } + } + } + + #[test] + fn test_backend_event() { + // Test case: + // - a backend event is received; and + // - the backend has pending RX data. + { + let test_ctx = TestContext::new(); + let mut ctx = test_ctx.create_epoll_handler_context(); + + ctx.handler.backend.set_pending_rx(true); + ctx.handler + .handle_event(BACKEND_EVENT, epoll::Events::EPOLLIN) + .unwrap(); + + // The backend should've received this event. + assert_eq!(ctx.handler.backend.evset, Some(epoll::Events::EPOLLIN)); + // TX queue processing should've been triggered. + assert_eq!(ctx.guest_txvq.used.idx.get(), 1); + // RX queue processing should've been triggered. + assert_eq!(ctx.guest_rxvq.used.idx.get(), 1); + } + + // Test case: + // - a backend event is received; and + // - the backend doesn't have any pending RX data. + { + let test_ctx = TestContext::new(); + let mut ctx = test_ctx.create_epoll_handler_context(); + + ctx.handler.backend.set_pending_rx(false); + ctx.handler + .handle_event(BACKEND_EVENT, epoll::Events::EPOLLIN) + .unwrap(); + + // The backend should've received this event. + assert_eq!(ctx.handler.backend.evset, Some(epoll::Events::EPOLLIN)); + // TX queue processing should've been triggered. + assert_eq!(ctx.guest_txvq.used.idx.get(), 1); + // The RX queue should've been left untouched. + assert_eq!(ctx.guest_rxvq.used.idx.get(), 0); + } + } + + #[test] + fn test_unknown_event() { + let test_ctx = TestContext::new(); + let mut ctx = test_ctx.create_epoll_handler_context(); + + match ctx.handler.handle_event(0xff, epoll::Events::EPOLLIN) { + Err(DeviceError::UnknownEvent { .. }) => (), + other => panic!("{:?}", other), + } + } +} diff --git a/devices/src/virtio/vsock/mod.rs b/devices/src/virtio/vsock/mod.rs new file mode 100644 index 00000000000..032b2ba8474 --- /dev/null +++ b/devices/src/virtio/vsock/mod.rs @@ -0,0 +1,400 @@ +// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +// +// Portions 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 THIRD-PARTY file. + +mod device; +mod epoll_handler; +mod packet; + +pub use self::defs::uapi::VIRTIO_ID_VSOCK as TYPE_VSOCK; +pub use self::defs::EVENT_COUNT as VSOCK_EVENTS_COUNT; +pub use self::device::Vsock; +pub use DummyBackend as VsockUnixBackend; + +use std::os::unix::io::RawFd; +use std::sync::mpsc; + +use memory_model::GuestMemoryError; + +use super::super::EpollHandler; +use super::EpollConfigConstructor; +use packet::VsockPacket; + +#[allow(dead_code)] +mod defs { + use crate::DeviceEventT; + + /// RX queue event: the driver added available buffers to the RX queue. + pub const RXQ_EVENT: DeviceEventT = 0; + /// TX queue event: the driver added available buffers to the RX queue. + pub const TXQ_EVENT: DeviceEventT = 1; + /// Event queue event: the driver added available buffers to the event queue. + pub const EVQ_EVENT: DeviceEventT = 2; + /// Backend event: the backend needs a kick. + pub const BACKEND_EVENT: DeviceEventT = 3; + /// Total number of events known to the vsock epoll handler. + pub const EVENT_COUNT: usize = 4; + + /// Number of virtio queues. + pub const NUM_QUEUES: usize = 3; + /// Virtio queue sizes, in number of descriptor chain heads. + /// There are 3 queues for a virtio device (in this order): RX, TX, Event + pub const QUEUE_SIZES: &[u16] = &[256; NUM_QUEUES]; + + /// Max vsock packet data/buffer size. + pub const MAX_PKT_BUF_SIZE: usize = 64 * 1024; + + pub mod uapi { + + /// Virtio feature flags. + /// Defined in `/include/uapi/linux/virtio_config.h`. + /// + /// The device processes available buffers in the same order in which the device + /// offers them. + pub const VIRTIO_F_IN_ORDER: usize = 35; + /// The device conforms to the virtio spec version 1.0. + pub const VIRTIO_F_VERSION_1: u32 = 32; + + /// Virtio vsock device ID. + /// Defined in `include/uapi/linux/virtio_ids.h`. + pub const VIRTIO_ID_VSOCK: u32 = 19; + + /// Vsock packet operation IDs. + /// Defined in `/include/uapi/linux/virtio_vsock.h`. + /// + /// Connection request. + pub const VSOCK_OP_REQUEST: u16 = 1; + /// Connection response. + pub const VSOCK_OP_RESPONSE: u16 = 2; + /// Connection reset. + pub const VSOCK_OP_RST: u16 = 3; + /// Connection clean shutdown. + pub const VSOCK_OP_SHUTDOWN: u16 = 4; + /// Connection data (read/write). + pub const VSOCK_OP_RW: u16 = 5; + /// Flow control credit update. + pub const VSOCK_OP_CREDIT_UPDATE: u16 = 6; + /// Flow control credit update request. + pub const VSOCK_OP_CREDIT_REQUEST: u16 = 7; + + /// Vsock packet flags. + /// Defined in `/include/uapi/linux/virtio_vsock.h`. + /// + /// Valid with a VSOCK_OP_SHUTDOWN packet: the packet sender will receive no more data. + pub const VSOCK_FLAGS_SHUTDOWN_RCV: u32 = 1; + /// Valid with a VSOCK_OP_SHUTDOWN packet: the packet sender will send no more data. + pub const VSOCK_FLAGS_SHUTDOWN_SEND: u32 = 2; + + /// Vsock packet type. + /// Defined in `/include/uapi/linux/virtio_vsock.h`. + /// + /// Stream / connection-oriented packet (the only currently valid type). + pub const VSOCK_TYPE_STREAM: u16 = 1; + + pub const VSOCK_HOST_CID: u64 = 2; + } +} + +#[derive(Debug)] +pub enum VsockError { + /// The vsock data/buffer virtio descriptor length is smaller than expected. + BufDescTooSmall, + /// The vsock data/buffer virtio descriptor is expected, but missing. + BufDescMissing, + /// Chained GuestMemory error. + GuestMemory(GuestMemoryError), + /// Bounds check failed on guest memory pointer. + GuestMemoryBounds, + /// The vsock header descriptor length is too small. + HdrDescTooSmall(u32), + /// The vsock header `len` field holds an invalid value. + InvalidPktLen(u32), + /// A data fetch was attempted when no data was available. + NoData, + /// A data buffer was expected for the provided packet, but it is missing. + PktBufMissing, + /// Encountered an unexpected write-only virtio descriptor. + UnreadableDescriptor, + /// Encountered an unexpected read-only virtio descriptor. + UnwritableDescriptor, +} +type Result = std::result::Result; + +pub struct EpollConfig { + rxq_token: u64, + txq_token: u64, + evq_token: u64, + backend_token: u64, + epoll_raw_fd: RawFd, + sender: mpsc::Sender>, +} + +impl EpollConfigConstructor for EpollConfig { + fn new(first_token: u64, epoll_raw_fd: RawFd, sender: mpsc::Sender>) -> Self { + EpollConfig { + rxq_token: first_token + u64::from(defs::RXQ_EVENT), + txq_token: first_token + u64::from(defs::TXQ_EVENT), + evq_token: first_token + u64::from(defs::EVQ_EVENT), + backend_token: first_token + u64::from(defs::BACKEND_EVENT), + epoll_raw_fd, + sender, + } + } +} + +/// A passive, event-driven object, that needs to be notified whenever an epoll-able event occurs. +/// An event-polling control loop will use `get_polled_fd()` and `get_polled_evset()` to query +/// the listener for the file descriptor and the set of events it's interested in. When such an +/// event occurs, the control loop will route the event to the listener via `notify()`. +/// +pub trait VsockEpollListener { + /// Get the file descriptor the listener needs polled. + fn get_polled_fd(&self) -> RawFd; + + /// Get the set of events for which the listener wants to be notified. + fn get_polled_evset(&self) -> epoll::Events; + + /// Notify the listener that one ore more events have occurred. + fn notify(&mut self, evset: epoll::Events); +} + +/// Any channel that handles vsock packet traffic: sending and receiving packets. Since we're +/// implementing the device model here, our responsibility is to always process the sending of +/// packets (i.e. the TX queue). So, any locally generated data, addressed to the driver (e.g. +/// a connection response or RST), will have to be queued, until we get to processing the RX queue. +/// +/// Note: `recv_pkt()` and `send_pkt()` are named analogous to `Read::read()` and `Write::write()`, +/// respectively. I.e. +/// - `recv_pkt(&mut pkt)` will read data from the channel, and place it into `pkt`; and +/// - `send_pkt(&pkt)` will fetch data from `pkt`, and place it into the channel. +pub trait VsockChannel { + /// Read/receive an incoming packet from the channel. + fn recv_pkt(&mut self, pkt: &mut VsockPacket) -> Result<()>; + + /// Write/send a packet through the channel. + fn send_pkt(&mut self, pkt: &VsockPacket) -> Result<()>; + + /// Checks whether there is pending incoming data inside the channel, meaning that a subsequent + /// call to `recv_pkt()` won't fail. + fn has_pending_rx(&self) -> bool; +} + +/// The vsock backend, which is basically an epoll-event-driven vsock channel, that needs to be +/// sendable through a mpsc channel (the latter due to how `vmm::EpollContext` works). +/// Currently, the only implementation we have is `crate::virtio::unix::muxer::VsockMuxer`, which +/// translates guest-side vsock connections to host-side Unix domain socket connections. +pub trait VsockBackend: VsockChannel + VsockEpollListener + Send {} + +/// Placeholder implementor for a future vsock backend. +pub struct DummyBackend {} +impl DummyBackend { + pub fn new(_cid: u64, _path: String) -> Result { + Ok(Self {}) + } +} +impl VsockEpollListener for DummyBackend { + fn get_polled_fd(&self) -> RawFd { + -1 + } + fn get_polled_evset(&self) -> epoll::Events { + epoll::Events::empty() + } + fn notify(&mut self, _evset: epoll::Events) {} +} +impl VsockChannel for DummyBackend { + fn recv_pkt(&mut self, _pkt: &mut VsockPacket) -> Result<()> { + Ok(()) + } + fn send_pkt(&mut self, _pkt: &VsockPacket) -> Result<()> { + Ok(()) + } + fn has_pending_rx(&self) -> bool { + false + } +} +impl VsockBackend for DummyBackend {} + +#[cfg(test)] +mod tests { + use super::epoll_handler::VsockEpollHandler; + use super::packet::VSOCK_PKT_HDR_SIZE; + use super::*; + + use std::os::unix::io::AsRawFd; + use std::sync::atomic::AtomicUsize; + use std::sync::Arc; + use sys_util::EventFd; + + use crate::virtio::queue::tests::VirtQueue as GuestQ; + use crate::virtio::{VIRTQ_DESC_F_NEXT, VIRTQ_DESC_F_WRITE}; + use memory_model::{GuestAddress, GuestMemory}; + + pub struct TestBackend { + pub evfd: EventFd, + pub rx_err: Option, + pub tx_err: Option, + pub pending_rx: bool, + pub rx_ok_cnt: usize, + pub tx_ok_cnt: usize, + pub evset: Option, + } + impl TestBackend { + pub fn new() -> Self { + Self { + evfd: EventFd::new().unwrap(), + rx_err: None, + tx_err: None, + pending_rx: false, + rx_ok_cnt: 0, + tx_ok_cnt: 0, + evset: None, + } + } + pub fn set_rx_err(&mut self, err: Option) { + self.rx_err = err; + } + pub fn set_tx_err(&mut self, err: Option) { + self.tx_err = err; + } + pub fn set_pending_rx(&mut self, prx: bool) { + self.pending_rx = prx; + } + } + impl VsockChannel for TestBackend { + fn recv_pkt(&mut self, _pkt: &mut VsockPacket) -> Result<()> { + match self.rx_err.take() { + None => { + self.rx_ok_cnt += 1; + Ok(()) + } + Some(e) => Err(e), + } + } + fn send_pkt(&mut self, _pkt: &VsockPacket) -> Result<()> { + match self.tx_err.take() { + None => { + self.tx_ok_cnt += 1; + Ok(()) + } + Some(e) => Err(e), + } + } + fn has_pending_rx(&self) -> bool { + self.pending_rx + } + } + impl VsockEpollListener for TestBackend { + fn get_polled_fd(&self) -> RawFd { + self.evfd.as_raw_fd() + } + fn get_polled_evset(&self) -> epoll::Events { + epoll::Events::EPOLLIN + } + fn notify(&mut self, evset: epoll::Events) { + self.evset = Some(evset); + } + } + impl VsockBackend for TestBackend {} + + pub struct TestContext { + pub cid: u64, + pub mem: GuestMemory, + pub mem_size: usize, + pub device: Vsock, + + // This needs to live here, so that sending the handler, at device activation, works. + _handler_receiver: mpsc::Receiver>, + } + + impl TestContext { + pub fn new() -> Self { + const CID: u64 = 52; + const MEM_SIZE: usize = 1024 * 1024 * 128; + let (sender, _handler_receiver) = mpsc::channel(); + Self { + cid: CID, + mem: GuestMemory::new(&[(GuestAddress(0), MEM_SIZE)]).unwrap(), + mem_size: MEM_SIZE, + device: Vsock::new( + CID, + EpollConfig::new(0, epoll::create(true).unwrap(), sender), + TestBackend::new(), + ) + .unwrap(), + _handler_receiver, + } + } + + pub fn create_epoll_handler_context(&self) -> EpollHandlerContext { + const QSIZE: u16 = 2; + + let guest_rxvq = GuestQ::new(GuestAddress(0x0010_0000), &self.mem, QSIZE as u16); + let guest_txvq = GuestQ::new(GuestAddress(0x0020_0000), &self.mem, QSIZE as u16); + let guest_evvq = GuestQ::new(GuestAddress(0x0030_0000), &self.mem, QSIZE as u16); + let rxvq = guest_rxvq.create_queue(); + let txvq = guest_txvq.create_queue(); + let evvq = guest_evvq.create_queue(); + + // Set up one available descriptor in the RX queue. + guest_rxvq.dtable[0].set( + 0x0040_0000, + VSOCK_PKT_HDR_SIZE as u32, + VIRTQ_DESC_F_WRITE | VIRTQ_DESC_F_NEXT, + 1, + ); + guest_rxvq.dtable[1].set(0x0040_1000, 4096, VIRTQ_DESC_F_WRITE, 0); + guest_rxvq.avail.ring[0].set(0); + guest_rxvq.avail.idx.set(1); + + // Set up one available descriptor in the TX queue. + guest_txvq.dtable[0].set(0x0050_0000, VSOCK_PKT_HDR_SIZE as u32, VIRTQ_DESC_F_NEXT, 1); + guest_txvq.dtable[1].set(0x0050_1000, 4096, 0, 0); + guest_txvq.avail.ring[0].set(0); + guest_txvq.avail.idx.set(1); + + EpollHandlerContext { + guest_rxvq, + guest_txvq, + guest_evvq, + handler: VsockEpollHandler { + rxvq, + rxvq_evt: EventFd::new().unwrap(), + txvq, + txvq_evt: EventFd::new().unwrap(), + evvq, + evvq_evt: EventFd::new().unwrap(), + cid: self.cid, + mem: self.mem.clone(), + interrupt_status: Arc::new(AtomicUsize::new(0)), + interrupt_evt: EventFd::new().unwrap(), + backend: TestBackend::new(), + }, + } + } + } + + pub struct EpollHandlerContext<'a> { + pub handler: VsockEpollHandler, + pub guest_rxvq: GuestQ<'a>, + pub guest_txvq: GuestQ<'a>, + pub guest_evvq: GuestQ<'a>, + } + + impl<'a> EpollHandlerContext<'a> { + pub fn signal_txq_event(&mut self) { + self.handler.txvq_evt.write(1).unwrap(); + self.handler + .handle_event(defs::TXQ_EVENT, epoll::Events::EPOLLIN) + .unwrap(); + } + pub fn signal_rxq_event(&mut self) { + self.handler.rxvq_evt.write(1).unwrap(); + self.handler + .handle_event(defs::RXQ_EVENT, epoll::Events::EPOLLIN) + .unwrap(); + } + } +} diff --git a/devices/src/virtio/vsock/packet.rs b/devices/src/virtio/vsock/packet.rs new file mode 100644 index 00000000000..f05df912ec1 --- /dev/null +++ b/devices/src/virtio/vsock/packet.rs @@ -0,0 +1,635 @@ +// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +// + +/// `VsockPacket` provides a thin wrapper over the buffers exchanged via virtio queues. +/// There are two components to a vsock packet, each using its own descriptor in a +/// virtio queue: +/// - the packet header; and +/// - the packet data/buffer. +/// There is a 1:1 relation between descriptor chains and packets: the first (chain head) holds +/// the header, and an optional second descriptor holds the data. The second descriptor is only +/// present for data packets (VSOCK_OP_RW). +/// +/// `VsockPacket` wraps these two buffers and provides direct access to the data stored +/// in guest memory. This is done to avoid unnecessarily copying data from guest memory +/// to temporary buffers, before passing it on to the vsock backend. +/// +use byteorder::{ByteOrder, LittleEndian}; + +use super::super::DescriptorChain; +use super::defs; +use super::{Result, VsockError}; + +// The vsock packet header is defined by the C struct: +// +// ```C +// struct virtio_vsock_hdr { +// le64 src_cid; +// le64 dst_cid; +// le32 src_port; +// le32 dst_port; +// le32 len; +// le16 type; +// le16 op; +// le32 flags; +// le32 buf_alloc; +// le32 fwd_cnt; +// }; +// ``` +// +// This structed will occupy the buffer pointed to by the head descriptor. We'll be accessing it +// as a byte slice. To that end, we define below the offsets for each field struct, as well as the +// packed struct size, as a bunch of `usize` consts. +// Note that these offsets are only used privately by the `VsockPacket` struct, the public interface +// consisting of getter and setter methods, for each struct field, that will also handle the correct +// endianess. + +/// The vsock packet header struct size (when packed). +pub const VSOCK_PKT_HDR_SIZE: usize = 44; + +// Source CID. +const HDROFF_SRC_CID: usize = 0; + +// Destination CID. +const HDROFF_DST_CID: usize = 8; + +// Source port. +const HDROFF_SRC_PORT: usize = 16; + +// Destination port. +const HDROFF_DST_PORT: usize = 20; + +// Data length (in bytes) - may be 0, if there is no data buffer. +const HDROFF_LEN: usize = 24; + +// Socket type. Currently, only connection-oriented streams are defined by the vsock protocol. +const HDROFF_TYPE: usize = 28; + +// Operation ID - one of the VSOCK_OP_* values; e.g. +// - VSOCK_OP_RW: a data packet; +// - VSOCK_OP_REQUEST: connection request; +// - VSOCK_OP_RST: forcefull connection termination; +// etc (see `super::defs::uapi` for the full list). +const HDROFF_OP: usize = 30; + +// Additional options (flags) associated with the current operation (`op`). +// Currently, only used with shutdown requests (VSOCK_OP_SHUTDOWN). +const HDROFF_FLAGS: usize = 32; + +// Size (in bytes) of the packet sender receive buffer (for the connection to which this packet +// belongs). +const HDROFF_BUF_ALLOC: usize = 36; + +// Number of bytes the sender has received and consumed (for the connection to which this packet +// belongs). For instance, for our Unix backend, this counter would be the total number of bytes +// we have successfully written to a backing Unix socket. +const HDROFF_FWD_CNT: usize = 40; + +/// The vsock packet, implemented as a wrapper over a virtq descriptor chain: +/// - the chain head, holding the packet header; and +/// - (an optional) data/buffer descriptor, only present for data packets (VSOCK_OP_RW). +/// +pub struct VsockPacket { + hdr: *mut u8, + buf: Option<*mut u8>, + buf_size: usize, +} + +impl VsockPacket { + /// Create the packet wrapper from a TX virtq chain head. + /// + /// The chain head is expected to hold valid packet header data. A following packet buffer + /// descriptor can optionally end the chain. Bounds and pointer checks are performed when + /// creating the wrapper. + /// + pub fn from_tx_virtq_head(head: &DescriptorChain) -> Result { + // All buffers in the TX queue must be readable. + // + if head.is_write_only() { + return Err(VsockError::UnreadableDescriptor); + } + + // The packet header should fit inside the head descriptor. + if head.len < VSOCK_PKT_HDR_SIZE as u32 { + return Err(VsockError::HdrDescTooSmall(head.len)); + } + + let mut pkt = Self { + hdr: head + .mem + .get_host_address(head.addr) + .map_err(VsockError::GuestMemory)? as *mut u8, + buf: None, + buf_size: 0, + }; + + // No point looking for a data/buffer descriptor, if the packet is zero-lengthed. + if pkt.len() == 0 { + return Ok(pkt); + } + + // Reject weirdly-sized packets. + // + if pkt.len() > defs::MAX_PKT_BUF_SIZE as u32 { + return Err(VsockError::InvalidPktLen(pkt.len())); + } + + // If the packet header showed a non-zero length, there should be a data descriptor here. + let buf_desc = head.next_descriptor().ok_or(VsockError::BufDescMissing)?; + + // TX data should be read-only. + if buf_desc.is_write_only() { + return Err(VsockError::UnreadableDescriptor); + } + + // The data buffer should be large enough to fit the size of the data, as described by + // the header descriptor. + if buf_desc.len < pkt.len() { + return Err(VsockError::BufDescTooSmall); + } + + pkt.buf_size = buf_desc.len as usize; + pkt.buf = Some( + buf_desc + .mem + .get_host_address(buf_desc.addr) + .map_err(VsockError::GuestMemory)? as *mut u8, + ); + + Ok(pkt) + } + + /// Create the packet wrapper from an RX virtq chain head. + /// + /// There must be two descriptors in the chain, both writable: a header descriptor and a data + /// descriptor. Bounds and pointer checks are performed when creating the wrapper. + /// + pub fn from_rx_virtq_head(head: &DescriptorChain) -> Result { + // All RX buffers must be writable. + // + if !head.is_write_only() { + return Err(VsockError::UnwritableDescriptor); + } + + // The packet header should fit inside the head descriptor. + if head.len < VSOCK_PKT_HDR_SIZE as u32 { + return Err(VsockError::HdrDescTooSmall(head.len)); + } + + // All RX descriptor chains should have a header and a data descriptor. + if !head.has_next() { + return Err(VsockError::BufDescMissing); + } + let buf_desc = head.next_descriptor().ok_or(VsockError::BufDescMissing)?; + + Ok(Self { + hdr: head + .mem + .get_host_address(head.addr) + .map_err(VsockError::GuestMemory)? as *mut u8, + buf: Some( + buf_desc + .mem + .get_host_address(buf_desc.addr) + .map_err(VsockError::GuestMemory)? as *mut u8, + ), + buf_size: buf_desc.len as usize, + }) + } + + /// Provides in-place, byte-slice, access to the vsock packet header. + /// + pub fn hdr(&self) -> &[u8] { + // This is safe since bound checks have already been performed when creating the packet + // from the virtq descriptor. + unsafe { std::slice::from_raw_parts(self.hdr as *const u8, VSOCK_PKT_HDR_SIZE) } + } + + /// Provides in-place, byte-slice, mutable access to the vsock packet header. + /// + pub fn hdr_mut(&mut self) -> &mut [u8] { + // This is safe since bound checks have already been performed when creating the packet + // from the virtq descriptor. + unsafe { std::slice::from_raw_parts_mut(self.hdr, VSOCK_PKT_HDR_SIZE) } + } + + /// Provides in-place, byte-slice access to the vsock packet data buffer. + /// + /// Note: control packets (e.g. connection request or reset) have no data buffer associated. + /// For those packets, this method will return `None`. + /// Also note: calling `len()` on the returned slice will yield the buffer size, which may be + /// (and often is) larger than the length of the packet data. The packet data length + /// is stored in the packet header, and accessible via `VsockPacket::len()`. + pub fn buf(&self) -> Option<&[u8]> { + self.buf.map(|ptr| { + // This is safe since bound checks have already been performed when creating the packet + // from the virtq descriptor. + unsafe { std::slice::from_raw_parts(ptr as *const u8, self.buf_size) } + }) + } + + /// Provides in-place, byte-slice, mutable access to the vsock packet data buffer. + /// + /// Note: control packets (e.g. connection request or reset) have no data buffer associated. + /// For those packets, this method will return `None`. + /// Also note: calling `len()` on the returned slice will yield the buffer size, which may be + /// (and often is) larger than the length of the packet data. The packet data length + /// is stored in the packet header, and accessible via `VsockPacket::len()`. + pub fn buf_mut(&mut self) -> Option<&mut [u8]> { + self.buf.map(|ptr| { + // This is safe since bound checks have already been performed when creating the packet + // from the virtq descriptor. + unsafe { std::slice::from_raw_parts_mut(ptr, self.buf_size) } + }) + } + + pub fn src_cid(&self) -> u64 { + LittleEndian::read_u64(&self.hdr()[HDROFF_SRC_CID..]) + } + + pub fn set_src_cid(&mut self, cid: u64) -> &mut Self { + LittleEndian::write_u64(&mut self.hdr_mut()[HDROFF_SRC_CID..], cid); + self + } + + pub fn dst_cid(&self) -> u64 { + LittleEndian::read_u64(&self.hdr()[HDROFF_DST_CID..]) + } + + pub fn set_dst_cid(&mut self, cid: u64) -> &mut Self { + LittleEndian::write_u64(&mut self.hdr_mut()[HDROFF_DST_CID..], cid); + self + } + + pub fn src_port(&self) -> u32 { + LittleEndian::read_u32(&self.hdr()[HDROFF_SRC_PORT..]) + } + + pub fn set_src_port(&mut self, port: u32) -> &mut Self { + LittleEndian::write_u32(&mut self.hdr_mut()[HDROFF_SRC_PORT..], port); + self + } + + pub fn dst_port(&self) -> u32 { + LittleEndian::read_u32(&self.hdr()[HDROFF_DST_PORT..]) + } + + pub fn set_dst_port(&mut self, port: u32) -> &mut Self { + LittleEndian::write_u32(&mut self.hdr_mut()[HDROFF_DST_PORT..], port); + self + } + + pub fn len(&self) -> u32 { + LittleEndian::read_u32(&self.hdr()[HDROFF_LEN..]) + } + + pub fn set_len(&mut self, len: u32) -> &mut Self { + LittleEndian::write_u32(&mut self.hdr_mut()[HDROFF_LEN..], len); + self + } + + pub fn type_(&self) -> u16 { + LittleEndian::read_u16(&self.hdr()[HDROFF_TYPE..]) + } + + pub fn set_type(&mut self, type_: u16) -> &mut Self { + LittleEndian::write_u16(&mut self.hdr_mut()[HDROFF_TYPE..], type_); + self + } + + pub fn op(&self) -> u16 { + LittleEndian::read_u16(&self.hdr()[HDROFF_OP..]) + } + + pub fn set_op(&mut self, op: u16) -> &mut Self { + LittleEndian::write_u16(&mut self.hdr_mut()[HDROFF_OP..], op); + self + } + + pub fn flags(&self) -> u32 { + LittleEndian::read_u32(&self.hdr()[HDROFF_FLAGS..]) + } + + pub fn set_flags(&mut self, flags: u32) -> &mut Self { + LittleEndian::write_u32(&mut self.hdr_mut()[HDROFF_FLAGS..], flags); + self + } + + pub fn set_flag(&mut self, flag: u32) -> &mut Self { + self.set_flags(self.flags() | flag); + self + } + + pub fn buf_alloc(&self) -> u32 { + LittleEndian::read_u32(&self.hdr()[HDROFF_BUF_ALLOC..]) + } + + pub fn set_buf_alloc(&mut self, buf_alloc: u32) -> &mut Self { + LittleEndian::write_u32(&mut self.hdr_mut()[HDROFF_BUF_ALLOC..], buf_alloc); + self + } + + pub fn fwd_cnt(&self) -> u32 { + LittleEndian::read_u32(&self.hdr()[HDROFF_FWD_CNT..]) + } + + pub fn set_fwd_cnt(&mut self, fwd_cnt: u32) -> &mut Self { + LittleEndian::write_u32(&mut self.hdr_mut()[HDROFF_FWD_CNT..], fwd_cnt); + self + } +} + +#[cfg(test)] +mod tests { + + use memory_model::{GuestAddress, GuestMemory}; + + use super::super::tests::TestContext; + use super::*; + use crate::virtio::queue::tests::VirtqDesc as GuestQDesc; + use crate::virtio::vsock::defs::MAX_PKT_BUF_SIZE; + use crate::virtio::VIRTQ_DESC_F_WRITE; + + macro_rules! create_context { + ($test_ctx:ident, $handler_ctx:ident) => { + let $test_ctx = TestContext::new(); + let mut $handler_ctx = $test_ctx.create_epoll_handler_context(); + // For TX packets, hdr.len should be set to a valid value. + set_pkt_len(1024, &$handler_ctx.guest_txvq.dtable[0], &$test_ctx.mem); + }; + } + + macro_rules! expect_asm_error { + (tx, $test_ctx:expr, $handler_ctx:expr, $err:pat) => { + expect_asm_error!($test_ctx, $handler_ctx, $err, from_tx_virtq_head, txvq); + }; + (rx, $test_ctx:expr, $handler_ctx:expr, $err:pat) => { + expect_asm_error!($test_ctx, $handler_ctx, $err, from_rx_virtq_head, rxvq); + }; + ($test_ctx:expr, $handler_ctx:expr, $err:pat, $ctor:ident, $vq:ident) => { + match VsockPacket::$ctor(&$handler_ctx.handler.$vq.pop(&$test_ctx.mem).unwrap()) { + Err($err) => (), + Ok(_) => panic!("Packet assembly should've failed!"), + Err(other) => panic!("Packet assembly failed with: {:?}", other), + } + }; + } + + fn set_pkt_len(len: u32, guest_desc: &GuestQDesc, mem: &GuestMemory) { + let hdr_gpa = guest_desc.addr.get() as usize; + let hdr_ptr = mem.get_host_address(GuestAddress(hdr_gpa)).unwrap() as *mut u8; + let len_ptr = unsafe { hdr_ptr.add(HDROFF_LEN) }; + + LittleEndian::write_u32(unsafe { std::slice::from_raw_parts_mut(len_ptr, 4) }, len); + } + + #[test] + #[allow(clippy::cognitive_complexity)] + fn test_tx_packet_assembly() { + // Test case: successful TX packet assembly. + { + create_context!(test_ctx, handler_ctx); + + let pkt = VsockPacket::from_tx_virtq_head( + &handler_ctx.handler.txvq.pop(&test_ctx.mem).unwrap(), + ) + .unwrap(); + assert_eq!(pkt.hdr().len(), VSOCK_PKT_HDR_SIZE); + assert_eq!( + pkt.buf().unwrap().len(), + handler_ctx.guest_txvq.dtable[1].len.get() as usize + ); + } + + // Test case: error on write-only hdr descriptor. + { + create_context!(test_ctx, handler_ctx); + handler_ctx.guest_txvq.dtable[0] + .flags + .set(VIRTQ_DESC_F_WRITE); + expect_asm_error!(tx, test_ctx, handler_ctx, VsockError::UnreadableDescriptor); + } + + // Test case: header descriptor has insufficient space to hold the packet header. + { + create_context!(test_ctx, handler_ctx); + handler_ctx.guest_txvq.dtable[0] + .len + .set(VSOCK_PKT_HDR_SIZE as u32 - 1); + expect_asm_error!(tx, test_ctx, handler_ctx, VsockError::HdrDescTooSmall(_)); + } + + // Test case: zero-length TX packet. + { + create_context!(test_ctx, handler_ctx); + set_pkt_len(0, &handler_ctx.guest_txvq.dtable[0], &test_ctx.mem); + let mut pkt = VsockPacket::from_tx_virtq_head( + &handler_ctx.handler.txvq.pop(&test_ctx.mem).unwrap(), + ) + .unwrap(); + assert!(pkt.buf().is_none()); + assert!(pkt.buf_mut().is_none()); + } + + // Test case: TX packet has more data than we can handle. + { + create_context!(test_ctx, handler_ctx); + set_pkt_len( + MAX_PKT_BUF_SIZE as u32 + 1, + &handler_ctx.guest_txvq.dtable[0], + &test_ctx.mem, + ); + expect_asm_error!(tx, test_ctx, handler_ctx, VsockError::InvalidPktLen(_)); + } + + // Test case: + // - packet header advertises some data length; and + // - the data descriptor is missing. + { + create_context!(test_ctx, handler_ctx); + set_pkt_len(1024, &handler_ctx.guest_txvq.dtable[0], &test_ctx.mem); + handler_ctx.guest_txvq.dtable[0].flags.set(0); + expect_asm_error!(tx, test_ctx, handler_ctx, VsockError::BufDescMissing); + } + + // Test case: error on write-only buf descriptor. + { + create_context!(test_ctx, handler_ctx); + handler_ctx.guest_txvq.dtable[1] + .flags + .set(VIRTQ_DESC_F_WRITE); + expect_asm_error!(tx, test_ctx, handler_ctx, VsockError::UnreadableDescriptor); + } + + // Test case: the buffer descriptor cannot fit all the data advertised by the the + // packet header `len` field. + { + create_context!(test_ctx, handler_ctx); + set_pkt_len(8 * 1024, &handler_ctx.guest_txvq.dtable[0], &test_ctx.mem); + handler_ctx.guest_txvq.dtable[1].len.set(4 * 1024); + expect_asm_error!(tx, test_ctx, handler_ctx, VsockError::BufDescTooSmall); + } + } + + #[test] + fn test_rx_packet_assembly() { + // Test case: successful RX packet assembly. + { + create_context!(test_ctx, handler_ctx); + let pkt = VsockPacket::from_rx_virtq_head( + &handler_ctx.handler.rxvq.pop(&test_ctx.mem).unwrap(), + ) + .unwrap(); + assert_eq!(pkt.hdr().len(), VSOCK_PKT_HDR_SIZE); + assert_eq!( + pkt.buf().unwrap().len(), + handler_ctx.guest_rxvq.dtable[1].len.get() as usize + ); + } + + // Test case: read-only RX packet header. + { + create_context!(test_ctx, handler_ctx); + handler_ctx.guest_rxvq.dtable[0].flags.set(0); + expect_asm_error!(rx, test_ctx, handler_ctx, VsockError::UnwritableDescriptor); + } + + // Test case: RX descriptor head cannot fit the entire packet header. + { + create_context!(test_ctx, handler_ctx); + handler_ctx.guest_rxvq.dtable[0] + .len + .set(VSOCK_PKT_HDR_SIZE as u32 - 1); + expect_asm_error!(rx, test_ctx, handler_ctx, VsockError::HdrDescTooSmall(_)); + } + + // Test case: RX descriptor chain is missing the packet buffer descriptor. + { + create_context!(test_ctx, handler_ctx); + handler_ctx.guest_rxvq.dtable[0] + .flags + .set(VIRTQ_DESC_F_WRITE); + expect_asm_error!(rx, test_ctx, handler_ctx, VsockError::BufDescMissing); + } + } + + #[test] + #[allow(clippy::cognitive_complexity)] + fn test_packet_hdr_accessors() { + const SRC_CID: u64 = 1; + const DST_CID: u64 = 2; + const SRC_PORT: u32 = 3; + const DST_PORT: u32 = 4; + const LEN: u32 = 5; + const TYPE: u16 = 6; + const OP: u16 = 7; + const FLAGS: u32 = 8; + const BUF_ALLOC: u32 = 9; + const FWD_CNT: u32 = 10; + + create_context!(test_ctx, handler_ctx); + let mut pkt = + VsockPacket::from_rx_virtq_head(&handler_ctx.handler.rxvq.pop(&test_ctx.mem).unwrap()) + .unwrap(); + + // Test field accessors. + pkt.set_src_cid(SRC_CID) + .set_dst_cid(DST_CID) + .set_src_port(SRC_PORT) + .set_dst_port(DST_PORT) + .set_len(LEN) + .set_type(TYPE) + .set_op(OP) + .set_flags(FLAGS) + .set_buf_alloc(BUF_ALLOC) + .set_fwd_cnt(FWD_CNT); + + assert_eq!(pkt.src_cid(), SRC_CID); + assert_eq!(pkt.dst_cid(), DST_CID); + assert_eq!(pkt.src_port(), SRC_PORT); + assert_eq!(pkt.dst_port(), DST_PORT); + assert_eq!(pkt.len(), LEN); + assert_eq!(pkt.type_(), TYPE); + assert_eq!(pkt.op(), OP); + assert_eq!(pkt.flags(), FLAGS); + assert_eq!(pkt.buf_alloc(), BUF_ALLOC); + assert_eq!(pkt.fwd_cnt(), FWD_CNT); + + // Test individual flag setting. + let flags = pkt.flags() | 0b1000; + pkt.set_flag(0b1000); + assert_eq!(pkt.flags(), flags); + + // Test packet header as-slice access. + // + + assert_eq!(pkt.hdr().len(), VSOCK_PKT_HDR_SIZE); + + assert_eq!( + SRC_CID, + LittleEndian::read_u64(&pkt.hdr()[HDROFF_SRC_CID..]) + ); + assert_eq!( + DST_CID, + LittleEndian::read_u64(&pkt.hdr()[HDROFF_DST_CID..]) + ); + assert_eq!( + SRC_PORT, + LittleEndian::read_u32(&pkt.hdr()[HDROFF_SRC_PORT..]) + ); + assert_eq!( + DST_PORT, + LittleEndian::read_u32(&pkt.hdr()[HDROFF_DST_PORT..]) + ); + assert_eq!(LEN, LittleEndian::read_u32(&pkt.hdr()[HDROFF_LEN..])); + assert_eq!(TYPE, LittleEndian::read_u16(&pkt.hdr()[HDROFF_TYPE..])); + assert_eq!(OP, LittleEndian::read_u16(&pkt.hdr()[HDROFF_OP..])); + assert_eq!(FLAGS, LittleEndian::read_u32(&pkt.hdr()[HDROFF_FLAGS..])); + assert_eq!( + BUF_ALLOC, + LittleEndian::read_u32(&pkt.hdr()[HDROFF_BUF_ALLOC..]) + ); + assert_eq!( + FWD_CNT, + LittleEndian::read_u32(&pkt.hdr()[HDROFF_FWD_CNT..]) + ); + + assert_eq!(pkt.hdr_mut().len(), VSOCK_PKT_HDR_SIZE); + for b in pkt.hdr_mut() { + *b = 0; + } + assert_eq!(pkt.src_cid(), 0); + assert_eq!(pkt.dst_cid(), 0); + assert_eq!(pkt.src_port(), 0); + assert_eq!(pkt.dst_port(), 0); + assert_eq!(pkt.len(), 0); + assert_eq!(pkt.type_(), 0); + assert_eq!(pkt.op(), 0); + assert_eq!(pkt.flags(), 0); + assert_eq!(pkt.buf_alloc(), 0); + assert_eq!(pkt.fwd_cnt(), 0); + } + + #[test] + fn test_packet_buf() { + create_context!(test_ctx, handler_ctx); + let mut pkt = + VsockPacket::from_rx_virtq_head(&handler_ctx.handler.rxvq.pop(&test_ctx.mem).unwrap()) + .unwrap(); + + assert_eq!( + pkt.buf().unwrap().len(), + handler_ctx.guest_rxvq.dtable[1].len.get() as usize + ); + assert_eq!( + pkt.buf_mut().unwrap().len(), + handler_ctx.guest_rxvq.dtable[1].len.get() as usize + ); + + for i in 0..pkt.buf().unwrap().len() { + pkt.buf_mut().unwrap()[i] = (i % 0x100) as u8; + assert_eq!(pkt.buf().unwrap()[i], (i % 0x100) as u8); + } + } +} diff --git a/docs/experimental-vsock.md b/docs/experimental-vsock.md deleted file mode 100644 index a03719e0a38..00000000000 --- a/docs/experimental-vsock.md +++ /dev/null @@ -1,37 +0,0 @@ -# Firecracker's experimental vsock support - -Firecracker offers experimental **vhost-based vsock** support which allows -one or multiple vsock devices to be attached to the microVM. As per the -`vsock(7)` man page, *the VSOCK address family facilitates communication -between virtual machines and the host they are running on. -This address family is used by guest agents and hypervisor services that -need a communications channel that is independent of virtual machine network -configuration*. - -## Obtaining the experimental binary - -```cargo build --features vsock``` - -## Attaching a vsock device - -``` -curl --unix-socket /tmp/firecracker.socket -i \ - -X PUT "http://localhost/vsocks/root" \ - -H "accept: application/json" \ - -H "Content-Type: application/json" \ - -d "{ - \"id\": \"root\", - \"guest_cid\": 3 - }" -``` - -- `id` is a string that uniquely identifies the current vsock device -- `guest_cid` represents an integer that must be `>=2` and `< UINT32_MAX` - -## Limitations - -Given that this is an experimental feature, we **do not** recommend including it -in production use. Enabling this vsock feature that is built on a vhost back-end -support adds an attack surface that bypasses the jailer barrier. In the near -future, we will add a non-vhost back-end for vsock. This feature is also not -covered by unit and integration tests. diff --git a/docs/jailer.md b/docs/jailer.md index d0f61f181fc..97f47de5d9d 100644 --- a/docs/jailer.md +++ b/docs/jailer.md @@ -69,12 +69,9 @@ After starting, the Jailer goes through the following operations: point, and call `chroot` into the current directory. - Use `mknod` to create a `/dev/net/tun` equivalent inside the jail. - Use `mknod` to create a `/dev/kvm` equivalent inside the jail. -- When compiled with `vsock` support, use `mknod` to create a - `/dev/vhost_vsock` equivalent inside the jail. - Use `chown` to change ownership of the `chroot_dir` (root path `/` as seen - by the jailed firecracker), `/dev/net/tun`, `/dev/kvm`, and if compiled with - `vsock` support `/dev/vhost_vsock`. The ownership is changed to the provided - `uid:gid`. + by the jailed firecracker), `/dev/net/tun`, `/dev/kvm`. The ownership is + changed to the provided `uid:gid`. - If `--netns ` is present, attempt to join the specified network namespace. - If `--daemonize` is specified, call `setsid()` and redirect `STDIN`, @@ -167,9 +164,8 @@ MNT_DETACH)`, deleting `old_root` with `rmdir`, and finally calling Create the special file `/dev/net/tun`, using `mknod(“/dev/net/tun”, S_IFCHR | S_IRUSR | S_IWUSR, makedev(10, 200))`, and then call `chown(“/dev/net/tun”, 123, 100)`, so Firecracker can use it after dropping privileges. This is -required to use multiple TAP interfaces when running jailed. -Do the same for `/dev/kvm` and, when compiled with `vsock` support, -`/dev/vhost_vsock`. +required to use multiple TAP interfaces when running jailed. Do the same for +`/dev/kvm`. Change ownership of `` to `uid:gid` so that Firecracker can create its API socket there. diff --git a/jailer/Cargo.toml b/jailer/Cargo.toml index 1748cc658bb..a214905b430 100644 --- a/jailer/Cargo.toml +++ b/jailer/Cargo.toml @@ -13,6 +13,3 @@ sys_util = { path = "../sys_util" } [dev-dependencies] tempfile = ">=3.0.2" - -[features] -vsock = [] diff --git a/jailer/src/env.rs b/jailer/src/env.rs index b9db6553146..c78d31c705b 100644 --- a/jailer/src/env.rs +++ b/jailer/src/env.rs @@ -24,8 +24,6 @@ const STDERR_FILENO: libc::c_int = 2; const DEV_KVM_WITH_NUL: &[u8] = b"/dev/kvm\0"; const DEV_NET_TUN_WITH_NUL: &[u8] = b"/dev/net/tun\0"; const DEV_NULL_WITH_NUL: &[u8] = b"/dev/null\0"; -#[cfg(feature = "vsock")] -const DEV_VHOST_VSOCK_WITH_NUL: &[u8] = b"/dev/vhost-vsock\0"; const ROOT_PATH_WITH_NUL: &[u8] = b"/\0"; // Helper function, since we'll use libc::dup2 a bunch of times for daemonization. @@ -263,9 +261,6 @@ impl Env { self.mknod_and_own_dev(DEV_NET_TUN_WITH_NUL, 10, 200)?; // Do the same for /dev/kvm with (major, minor) = (10, 232). self.mknod_and_own_dev(DEV_KVM_WITH_NUL, 10, 232)?; - #[cfg(feature = "vsock")] - // Do the same for /dev/vhost_vsock with (major, minor) = (10, 241). - self.mknod_and_own_dev(DEV_VHOST_VSOCK_WITH_NUL, 10, 241)?; // Change ownership of the jail root to Firecracker's UID and GID. This is necessary // so Firecracker can create the unix domain socket in its own jail. diff --git a/tests/conftest.py b/tests/conftest.py index e8da87ef579..d43f3d16f24 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -121,12 +121,6 @@ def test_with_any_microvm(test_microvm_any): IP4_GENERATOR_CREATE_LOCK = threading.Lock() -MICROVM_FIXTURE_PARAMS = ['', 'vsock'] -# Since this is a temporary feature, we do not test -# it on aarch64. -if platform.machine() == "aarch64": - MICROVM_FIXTURE_PARAMS = [''] - # This codebase uses Python features available in Python 3.6 or above if sys.version_info < (3, 6): @@ -223,7 +217,6 @@ def aux_bin_paths(test_session_root_path): They currently consist of: * a binary that can properly use the `clone()` syscall; - * a very simple vsock client/server application; * a jailer with a simple syscall whitelist; * a jailer with a (syscall, arguments) advanced whitelist; * a jailed binary that follows the seccomp rules; @@ -236,11 +229,6 @@ def aux_bin_paths(test_session_root_path): 'host_tools/newpid_cloner.c', cloner_bin_path ) - test_vsock_bin_path = os.path.join(test_session_root_path, 'test_vsock') - _gcc_compile( - "host_tools/test_vsock.c", - test_vsock_bin_path - ) seccomp_build_path = os.path.join( test_session_root_path, @@ -286,7 +274,6 @@ def aux_bin_paths(test_session_root_path): yield { 'cloner': cloner_bin_path, - 'test_vsock': test_vsock_bin_path, 'demo_basic_jailer': demo_basic_jailer, 'demo_advanced_jailer': demo_advanced_jailer, 'demo_harmless': demo_harmless, @@ -294,8 +281,8 @@ def aux_bin_paths(test_session_root_path): } -@pytest.fixture(params=MICROVM_FIXTURE_PARAMS) -def microvm(request, test_session_root_path, aux_bin_paths): +@pytest.fixture +def microvm(test_session_root_path, aux_bin_paths): """Instantiate a microvm.""" # pylint: disable=redefined-outer-name # The fixture pattern causes a pylint false positive for that rule. @@ -305,7 +292,7 @@ def microvm(request, test_session_root_path, aux_bin_paths): vm = init_microvm( test_session_root_path, aux_bin_paths, - features=request.param + features='' ) yield vm vm.kill() diff --git a/tests/framework/microvm.py b/tests/framework/microvm.py index eb7e3dbc751..6d06b88b77f 100644 --- a/tests/framework/microvm.py +++ b/tests/framework/microvm.py @@ -51,8 +51,6 @@ def __init__( # Unique identifier for this machine. self._microvm_id = microvm_id - # This is used in tests to identify if the microvm was started - # using a vsock build or a default build. self.build_feature = build_feature # Compose the paths to the resources specific to this microvm. @@ -249,10 +247,6 @@ def spawn(self): self._api_socket = self._jailer.api_socket_path() self._api_session = Session() - # Don't time requests on vsock builds. - if self.build_feature == 'vsock': - self._api_session.untime() - self.actions = Actions(self._api_socket, self._api_session) self.boot = BootSource(self._api_socket, self._api_session) self.drive = Drive(self._api_socket, self._api_session) diff --git a/tests/framework/resources.py b/tests/framework/resources.py index cec8f311771..c6941fd1c0d 100644 --- a/tests/framework/resources.py +++ b/tests/framework/resources.py @@ -419,12 +419,14 @@ def patch(cls, **args): @staticmethod def create_json( vsock_id, - guest_cid + guest_cid, + uds_path ): """Create the json for the vsock specific API request.""" datax = { - 'id': vsock_id, - 'guest_cid': guest_cid + 'vsock_id': vsock_id, + 'guest_cid': guest_cid, + 'uds_path': uds_path } return datax diff --git a/tests/host_tools/cargo_build.py b/tests/host_tools/cargo_build.py index 12a1107372f..4834fb323c8 100644 --- a/tests/host_tools/cargo_build.py +++ b/tests/host_tools/cargo_build.py @@ -17,10 +17,6 @@ ) """Keep a single Firecracker release binary path across all test types.""" -CARGO_RELEASE_VSOCK_REL_PATH = os.path.join( - CARGO_BUILD_REL_PATH, 'release-vsock' -) -"""Vsock release binaries relative path.""" DEFAULT_BUILD_TARGET = '{}-unknown-linux-musl'.format(platform.machine()) RELEASE_BINARIES_REL_PATH = '{}/release/'.format(DEFAULT_BUILD_TARGET) @@ -73,9 +69,6 @@ def get_firecracker_binaries(root_path, features=''): if features == '': cargo_binaries_rel_path = CARGO_RELEASE_REL_PATH - elif features == 'vsock': - cargo_binaries_rel_path = CARGO_RELEASE_VSOCK_REL_PATH - extra_args = '--features vsock ' + extra_args else: raise UnknownFeatureException diff --git a/tests/host_tools/test_vsock.c b/tests/host_tools/test_vsock.c deleted file mode 100644 index 714de90aec1..00000000000 --- a/tests/host_tools/test_vsock.c +++ /dev/null @@ -1,90 +0,0 @@ -#include -#include -#include -#include -#include -#include - -#define SERVER_PORT 1100 - -static const int msg = 1235; - -int print_usage() { - printf("Usage: ./vsock_test { client | server }\n"); - return -1; -} - -int main(int argc, char **argv) { - if (argc < 2) { - return print_usage(); - } - - struct sockaddr_vm vsock_addr = { - .svm_family = AF_VSOCK, - .svm_port = SERVER_PORT, - }; - - int vsock = socket(AF_VSOCK, SOCK_STREAM, 0); - if (vsock < 0) { - perror("socket()"); - return -1; - } - - if (strcmp(argv[1], "client") == 0) { - if (argc < 3) { - return print_usage(); - } - - vsock_addr.svm_cid = atoi(argv[2]); - - if (connect(vsock, (struct sockaddr*)&vsock_addr, sizeof(vsock_addr)) != 0) { - perror("connect"); - return -1; - } - - int val; - // Since we're only reading an int, we assume any successful read got the - // entire value from the other side. - if (read(vsock, &val, sizeof(val)) < 0) { - perror("read"); - return -1; - } - - printf("%d\n", val); - } - else if (strcmp(argv[1], "server") == 0) { - vsock_addr.svm_cid = VMADDR_CID_ANY; - - if (bind(vsock, (struct sockaddr*)&vsock_addr, sizeof(vsock_addr)) != 0) { - perror("bind"); - return -1; - } - - if (listen(vsock, 1) != 0) { - perror("listen"); - return -1; - } - - struct sockaddr_vm client_addr; - socklen_t socklen_client = sizeof(client_addr); - - int client_vsock = accept(vsock, (struct sockaddr*)&client_addr, &socklen_client); - - if (client_vsock < 0) { - perror("accept"); - return -1; - } - - // Since we're only writing an int, we assume any successful write sent the - // entire value to the other side. - if (write(client_vsock, &msg, sizeof(int)) < 0) { - perror("write"); - return -1; - } - } - else { - return print_usage(); - } - - return 0; -} diff --git a/tests/integration_tests/build/test_build.py b/tests/integration_tests/build/test_build.py index c1c2e47e889..642d7ee1a38 100644 --- a/tests/integration_tests/build/test_build.py +++ b/tests/integration_tests/build/test_build.py @@ -11,13 +11,7 @@ import host_tools.cargo_build as host # pylint:disable=import-error MACHINE = platform.machine() -FEATURES = ["", "vsock"] - -# Since this is a temporary feature, we do not test -# it on aarch64. -if MACHINE == "aarch64": - FEATURES = [""] - +FEATURES = [""] BUILD_TYPES = ["debug", "release"] TARGETS = ["{}-unknown-linux-gnu".format(MACHINE), @@ -49,8 +43,7 @@ def test_build(test_session_root_path, features, build_type, target): # (either release or debug) and if any features are provided also using # the features names. # For example, a default release build with no features will end up in - # the relative directory "release", but for a vsock release build the - # relative directory will be "release-vsock". + # the relative directory "release". rel_path = os.path.join( host.CARGO_BUILD_REL_PATH, build_type diff --git a/tests/integration_tests/build/test_coverage.py b/tests/integration_tests/build/test_coverage.py index e6131886831..8a635e6bc34 100644 --- a/tests/integration_tests/build/test_coverage.py +++ b/tests/integration_tests/build/test_coverage.py @@ -19,7 +19,7 @@ import host_tools.cargo_build as host # pylint: disable=import-error -COVERAGE_TARGET_PCT = 84.2 +COVERAGE_TARGET_PCT = 85.5 COVERAGE_MAX_DELTA = 0.01 CARGO_KCOV_REL_PATH = os.path.join(host.CARGO_BUILD_REL_PATH, 'kcov') diff --git a/tests/integration_tests/build/test_style.py b/tests/integration_tests/build/test_style.py index 8864969ac13..5e11f4e4260 100644 --- a/tests/integration_tests/build/test_style.py +++ b/tests/integration_tests/build/test_style.py @@ -115,17 +115,3 @@ def test_firecracker_swagger(): os.path.join(os.getcwd(), '../api_server/swagger/firecracker.yaml') ) check_swagger_style(yaml_spec) - - -@pytest.mark.skipif( - platform.machine() != "x86_64", - reason="no need to test it on multiple platforms" -) -def test_experimental_firecracker_swagger(): - """Fail if experimental Firecracker swagger specification is malformed.""" - yaml_spec = os.path.normpath( - os.path.join( - os.getcwd(), - '../api_server/swagger/firecracker-experimental.yaml') - ) - check_swagger_style(yaml_spec) diff --git a/tests/integration_tests/functional/test_api.py b/tests/integration_tests/functional/test_api.py index 48bffb8d542..a2626b66fe5 100644 --- a/tests/integration_tests/functional/test_api.py +++ b/tests/integration_tests/functional/test_api.py @@ -724,30 +724,22 @@ def _drive_patch(test_microvm): def test_api_vsock(test_microvm_with_api): """Test vsock related API commands.""" - if test_microvm_with_api.build_feature != 'vsock': - pytest.skip("This test is meant only for vsock builds.") - test_microvm = test_microvm_with_api test_microvm.spawn() test_microvm.basic_config() response = test_microvm.vsock.put( vsock_id='vsock1', - guest_cid=15 - ) - assert test_microvm.api_session.is_status_no_content(response.status_code) - - # Adding another vsock should be fine. - response = test_microvm.vsock.put( - vsock_id='vsock2', - guest_cid=16 + guest_cid=15, + uds_path='vsock.sock' ) assert test_microvm.api_session.is_status_no_content(response.status_code) # Updating an existing vsock is currently fine. response = test_microvm.vsock.put( - vsock_id='vsock2', - guest_cid=166 + vsock_id='vsock1', + guest_cid=166, + uds_path='vsock.sock' ) assert test_microvm.api_session.is_status_no_content(response.status_code) @@ -757,13 +749,15 @@ def test_api_vsock(test_microvm_with_api): # Updating an existing vsock should not be fine at this point. response = test_microvm.vsock.put( vsock_id='vsock1', - guest_cid=17 + guest_cid=17, + uds_path='vsock.sock' ) assert test_microvm.api_session.is_status_bad_request(response.status_code) # Attaching a new vsock device should not be fine at this point. response = test_microvm.vsock.put( vsock_id='vsock3', - guest_cid=18 + guest_cid=18, + uds_path='vsock.sock' ) assert test_microvm.api_session.is_status_bad_request(response.status_code) diff --git a/tests/integration_tests/functional/test_vsock.py b/tests/integration_tests/functional/test_vsock.py deleted file mode 100644 index bf5e6ad11f5..00000000000 --- a/tests/integration_tests/functional/test_vsock.py +++ /dev/null @@ -1,61 +0,0 @@ -# Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 -"""Contains vsock functional tests.""" - -import threading - -from subprocess import run -from time import sleep - -import pytest - -import host_tools.network as net_tools - - -def test_vsock_ping_pong(test_microvm_with_ssh, network_config, aux_bin_paths): - """Test a vsock device. - - Creates a VM which has a vsock device attached, and them attempts - to communicate over a vsock connection using a simple client/server app. - """ - vm = test_microvm_with_ssh - if vm.build_feature != 'vsock': - pytest.skip("This test is meant only for vsock builds") - - vm.spawn() - vm.basic_config() - _tap, _, _ = vm.ssh_network_config(network_config, '1') - - response = vm.vsock.put( - vsock_id='vsock1', - guest_cid=100 - ) - assert vm.api_session.is_good_response(response.status_code) - - vm.start() - - test_vsock = aux_bin_paths['test_vsock'] - remote_test_vsock = '/tmp/test_vsock' - - ssh_connection = net_tools.SSHConnection(vm.ssh_config) - ssh_connection.scp_file(test_vsock, remote_test_vsock) - - server_thread = threading.Thread( - target=(lambda: run( - '{} server'.format(test_vsock), - shell=True, - check=True) - ) - ) - server_thread.start() - - # Wait for the server to start. - sleep(1) - - guest_cmd = '{0} client 2 ; rm {0}'.format(remote_test_vsock) - - _, stdout, stderr = ssh_connection.execute_command(guest_cmd) - assert stderr.read().decode('utf-8') == '' - assert stdout.read().decode('utf-8').strip() == '1235' - - server_thread.join() diff --git a/tests/integration_tests/performance/test_boottime.py b/tests/integration_tests/performance/test_boottime.py index e96421cd345..a8b6324bb34 100644 --- a/tests/integration_tests/performance/test_boottime.py +++ b/tests/integration_tests/performance/test_boottime.py @@ -6,8 +6,6 @@ import re import time -import pytest - from framework import decorators import host_tools.logging as log_tools @@ -22,10 +20,6 @@ def test_single_microvm_boottime_no_network(test_microvm_with_boottime): """Check guest boottime of microvm without network.""" - # Skip on vsock builds. - if test_microvm_with_boottime.build_feature == 'vsock': - pytest.skip('The vsock feature is enabled.') - log_fifo, _ = _configure_vm(test_microvm_with_boottime) time.sleep(0.4) boottime_us = _test_microvm_boottime(log_fifo) @@ -74,10 +68,6 @@ def test_single_microvm_boottime_with_network( network_config ): """Check guest boottime of microvm with network.""" - # Skip on vsock builds. - if test_microvm_with_boottime.build_feature == 'vsock': - pytest.skip('The vsock feature is enabled.') - log_fifo, _tap = _configure_vm(test_microvm_with_boottime, { "config": network_config, "iface_id": "1" }) diff --git a/vhost_backend/Cargo.toml b/vhost_backend/Cargo.toml deleted file mode 100644 index 573623506ea..00000000000 --- a/vhost_backend/Cargo.toml +++ /dev/null @@ -1,11 +0,0 @@ -[package] -name = "vhost_backend" -version = "0.1.0" -authors = ["The Chromium OS Authors"] - -[dependencies] -libc = ">=0.2.39" - -memory_model = { path = "../memory_model" } -vhost_gen = { path = "../vhost_gen" } -sys_util = { path = "../sys_util" } diff --git a/vhost_backend/src/lib.rs b/vhost_backend/src/lib.rs deleted file mode 100644 index 0a36c5e6de3..00000000000 --- a/vhost_backend/src/lib.rs +++ /dev/null @@ -1,304 +0,0 @@ -// 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 THIRD-PARTY file. - -extern crate libc; - -extern crate memory_model; -extern crate sys_util; -extern crate vhost_gen; - -mod vsock; -pub use vsock::Vsock; - -use std::mem; -use std::os::unix::io::AsRawFd; -use std::ptr::null; - -use memory_model::{GuestAddress, GuestMemory, GuestMemoryError}; -use sys_util::{ioctl, ioctl_with_mut_ref, ioctl_with_ptr, ioctl_with_ref, EventFd}; -use vhost_gen::*; - -#[derive(Debug)] -pub enum Error { - /// Error opening vhost device. - VhostOpen(std::io::Error), - /// Error while running ioctl. - IoctlError(std::io::Error), - /// Invalid queue. - InvalidQueue, - /// Invalid descriptor table address. - DescriptorTableAddress(GuestMemoryError), - /// Invalid used address. - UsedAddress(GuestMemoryError), - /// Invalid available address. - AvailAddress(GuestMemoryError), - /// Invalid log address. - LogAddress(GuestMemoryError), -} -pub type Result = std::result::Result; - -fn ioctl_error() -> Result { - Err(Error::IoctlError(std::io::Error::last_os_error())) -} - -/// An interface for setting up vhost-based virtio devices. Vhost-based devices are different -/// from regular virtio devices because the host kernel takes care of handling all the data -/// transfer. The device itself only needs to deal with setting up the kernel driver and -/// managing the control channel. -pub trait Vhost: AsRawFd + std::marker::Sized { - /// Get the guest memory mapping. - fn mem(&self) -> &GuestMemory; - - /// Set the current process as the owner of this file descriptor. - /// This must be run before any other vhost ioctls. - fn set_owner(&self) -> Result<()> { - // This ioctl is called on a valid vhost fd and has its - // return value checked. - let ret = unsafe { ioctl(self, VHOST_SET_OWNER()) }; - if ret < 0 { - return ioctl_error(); - } - Ok(()) - } - - /// Get a bitmask of supported virtio/vhost features. - fn get_features(&self) -> Result { - let mut avail_features: u64 = 0; - // This ioctl is called on a valid vhost fd and has its - // return value checked. - let ret = unsafe { ioctl_with_mut_ref(self, VHOST_GET_FEATURES(), &mut avail_features) }; - if ret < 0 { - return ioctl_error(); - } - Ok(avail_features) - } - - /// Inform the vhost subsystem which features to enable. This should be a subset of - /// supported features from VHOST_GET_FEATURES. - /// - /// # Arguments - /// * `features` - Bitmask of features to set. - fn set_features(&self, features: u64) -> Result<()> { - // This ioctl is called on a valid vhost fd and has its - // return value checked. - let ret = unsafe { ioctl_with_ref(self, VHOST_SET_FEATURES(), &features) }; - if ret < 0 { - return ioctl_error(); - } - Ok(()) - } - - /// Set the guest memory mappings for vhost to use. - fn set_mem_table(&self) -> Result<()> { - let num_regions = self.mem().num_regions() as usize; - let vec_size_bytes = - mem::size_of::() + (num_regions * mem::size_of::()); - let mut bytes: Vec = vec![0; vec_size_bytes]; - // Convert bytes pointer to a vhost_memory mut ref. The vector has been - // sized correctly to ensure it can hold vhost_memory and N regions. - #[allow(clippy::cast_ptr_alignment)] - let vhost_memory: &mut vhost_memory = - unsafe { &mut *(bytes.as_mut_ptr() as *mut vhost_memory) }; - vhost_memory.nregions = num_regions as u32; - // regions is a zero-length array, so taking a mut slice requires that - // we correctly specify the size to match the amount of backing memory. - let vhost_regions = unsafe { vhost_memory.regions.as_mut_slice(num_regions as usize) }; - - let _ = self - .mem() - .with_regions_mut::<_, ()>(|index, guest_addr, size, host_addr| { - vhost_regions[index] = vhost_memory_region { - guest_phys_addr: guest_addr.offset() as u64, - memory_size: size as u64, - userspace_addr: host_addr as u64, - flags_padding: 0u64, - }; - Ok(()) - }); - - // This ioctl is called with a pointer that is valid for the lifetime - // of this function. The kernel will make its own copy of the memory - // tables. As always, check the return value. - let ret = unsafe { ioctl_with_ptr(self, VHOST_SET_MEM_TABLE(), bytes.as_ptr()) }; - if ret < 0 { - return ioctl_error(); - } - Ok(()) - } - - /// Set the number of descriptors in the vring. - /// - /// # Arguments - /// * `queue_index` - Index of the queue to set descriptor count for. - /// * `num` - Number of descriptors in the queue. - fn set_vring_num(&self, queue_index: usize, num: u16) -> Result<()> { - let vring_state = vhost_vring_state { - index: queue_index as u32, - num: u32::from(num), - }; - - // This ioctl is called on a valid vhost fd and has its - // return value checked. - let ret = unsafe { ioctl_with_ref(self, VHOST_SET_VRING_NUM(), &vring_state) }; - if ret < 0 { - return ioctl_error(); - } - Ok(()) - } - - // TODO(dpopa): Investigate how we can call is_valid directly on the Queue struct - fn is_valid( - &self, - queue_max_size: u16, - queue_size: u16, - desc_addr: GuestAddress, - avail_addr: GuestAddress, - used_addr: GuestAddress, - ) -> bool { - let desc_table_size = 16 * queue_size as usize; - let avail_ring_size = 6 + 2 * queue_size as usize; - let used_ring_size = 6 + 8 * queue_size as usize; - !(queue_size > queue_max_size - || queue_size == 0 - || (queue_size & (queue_size - 1)) != 0 - || desc_addr - .checked_add(desc_table_size) - .map_or(true, |v| !self.mem().address_in_range(v)) - || avail_addr - .checked_add(avail_ring_size) - .map_or(true, |v| !self.mem().address_in_range(v)) - || used_addr - .checked_add(used_ring_size) - .map_or(true, |v| !self.mem().address_in_range(v))) - } - - /// Set the addresses for a given vring. - /// - /// # Arguments - /// * `queue_max_size` - Maximum queue size supported by the device. - /// * `queue_size` - Actual queue size negotiated by the driver. - /// * `queue_index` - Index of the queue to set addresses for. - /// * `flags` - Bitmask of vring flags. - /// * `desc_table_addr` - Descriptor table address. - /// * `used_ring_addr` - Used ring buffer address. - /// * `avail_ring_addr` - Available ring buffer address. - /// * `log_addr` - Optional address for logging. - #[allow(clippy::too_many_arguments)] - fn set_vring_addr( - &self, - queue_max_size: u16, - queue_size: u16, - queue_index: usize, - flags: u32, - desc_table_addr: GuestAddress, - used_ring_addr: GuestAddress, - avail_ring_addr: GuestAddress, - log_addr: Option, - ) -> Result<()> { - if !self.is_valid( - queue_max_size, - queue_size, - desc_table_addr, - used_ring_addr, - avail_ring_addr, - ) { - return Err(Error::InvalidQueue); - } - - let desc_addr = self - .mem() - .get_host_address(desc_table_addr) - .map_err(Error::DescriptorTableAddress)?; - let used_addr = self - .mem() - .get_host_address(used_ring_addr) - .map_err(Error::UsedAddress)?; - let avail_addr = self - .mem() - .get_host_address(avail_ring_addr) - .map_err(Error::AvailAddress)?; - let log_addr = match log_addr { - None => null(), - Some(a) => self.mem().get_host_address(a).map_err(Error::LogAddress)?, - }; - - let vring_addr = vhost_vring_addr { - index: queue_index as u32, - flags, - desc_user_addr: desc_addr as u64, - used_user_addr: used_addr as u64, - avail_user_addr: avail_addr as u64, - log_guest_addr: log_addr as u64, - }; - - // This ioctl is called on a valid vhost fd and has its - // return value checked. - let ret = unsafe { ioctl_with_ref(self, VHOST_SET_VRING_ADDR(), &vring_addr) }; - if ret < 0 { - return ioctl_error(); - } - Ok(()) - } - - /// Set the first index to look for available descriptors. - /// - /// # Arguments - /// * `queue_index` - Index of the queue to modify. - /// * `num` - Index where available descriptors start. - fn set_vring_base(&self, queue_index: usize, num: u16) -> Result<()> { - let vring_state = vhost_vring_state { - index: queue_index as u32, - num: u32::from(num), - }; - - // This ioctl is called on a valid vhost fd and has its - // return value checked. - let ret = unsafe { ioctl_with_ref(self, VHOST_SET_VRING_BASE(), &vring_state) }; - if ret < 0 { - return ioctl_error(); - } - Ok(()) - } - - /// Set the eventfd to trigger when buffers have been used by the host. - /// - /// # Arguments - /// * `queue_index` - Index of the queue to modify. - /// * `fd` - EventFd to trigger. - fn set_vring_call(&self, queue_index: usize, fd: &EventFd) -> Result<()> { - let vring_file = vhost_vring_file { - index: queue_index as u32, - fd: fd.as_raw_fd(), - }; - - // This ioctl is called on a valid vhost fd and has its - // return value checked. - let ret = unsafe { ioctl_with_ref(self, VHOST_SET_VRING_CALL(), &vring_file) }; - if ret < 0 { - return ioctl_error(); - } - Ok(()) - } - - /// Set the eventfd that will be signaled by the guest when buffers are - /// available for the host to process. - /// - /// # Arguments - /// * `queue_index` - Index of the queue to modify. - /// * `fd` - EventFd that will be signaled from guest. - fn set_vring_kick(&self, queue_index: usize, fd: &EventFd) -> Result<()> { - let vring_file = vhost_vring_file { - index: queue_index as u32, - fd: fd.as_raw_fd(), - }; - - // This ioctl is called on a valid vhost fd and has its - // return value checked. - let ret = unsafe { ioctl_with_ref(self, VHOST_SET_VRING_KICK(), &vring_file) }; - if ret < 0 { - return ioctl_error(); - } - Ok(()) - } -} diff --git a/vhost_backend/src/vsock.rs b/vhost_backend/src/vsock.rs deleted file mode 100644 index f95d1586fa2..00000000000 --- a/vhost_backend/src/vsock.rs +++ /dev/null @@ -1,81 +0,0 @@ -// 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 THIRD-PARTY file. - -use libc; -use std::fs::{File, OpenOptions}; -use std::os::unix::fs::OpenOptionsExt; -use std::os::unix::io::{AsRawFd, RawFd}; - -use super::{ioctl_error, Error, Result, Vhost}; -use memory_model::GuestMemory; -use sys_util::ioctl_with_ref; -use vhost_gen::*; - -const VHOST_PATH: &str = "/dev/vhost-vsock"; - -/// Handle for running VHOST_VSOCK ioctls. -pub struct Vsock { - fd: File, - mem: GuestMemory, -} - -impl Vsock { - /// Open a handle to a new VHOST-VSOCK instance. - pub fn new(mem: &GuestMemory) -> Result { - Ok(Vsock { - fd: OpenOptions::new() - .read(true) - .write(true) - .custom_flags(libc::O_CLOEXEC | libc::O_NONBLOCK) - .open(VHOST_PATH) - .map_err(Error::VhostOpen)?, - mem: mem.clone(), - }) - } - - /// Set the CID for the guest. This number is used for routing all data destined for - /// running in the guest. Each guest on a hypervisor must have an unique CID - /// - /// # Arguments - /// * `cid` - CID to assign to the guest - pub fn set_guest_cid(&self, cid: u64) -> Result<()> { - let ret = unsafe { ioctl_with_ref(&self.fd, VHOST_VSOCK_SET_GUEST_CID(), &cid) }; - if ret < 0 { - return ioctl_error(); - } - Ok(()) - } - - /// Tell the VHOST driver to start performing data transfer. - pub fn start(&self) -> Result<()> { - self.set_running(true) - } - - /// Tell the VHOST driver to stop performing data transfer. - pub fn stop(&self) -> Result<()> { - self.set_running(false) - } - - fn set_running(&self, running: bool) -> Result<()> { - let on: ::std::os::raw::c_int = if running { 1 } else { 0 }; - let ret = unsafe { ioctl_with_ref(&self.fd, VHOST_VSOCK_SET_RUNNING(), &on) }; - - if ret < 0 { - return ioctl_error(); - } - Ok(()) - } -} - -impl Vhost for Vsock { - fn mem(&self) -> &GuestMemory { - &self.mem - } -} - -impl AsRawFd for Vsock { - fn as_raw_fd(&self) -> RawFd { - self.fd.as_raw_fd() - } -} diff --git a/vhost_gen/Cargo.toml b/vhost_gen/Cargo.toml deleted file mode 100644 index b665b7d08c0..00000000000 --- a/vhost_gen/Cargo.toml +++ /dev/null @@ -1,9 +0,0 @@ -[package] -name = "vhost_gen" -version = "0.1.0" -authors = ["Amazon Firecracker team "] - -[dependencies] -libc = ">=0.2.39" - -sys_util = { path = "../sys_util" } diff --git a/vhost_gen/patches/0001-vhost-fix-alignment-for-vhost_memory.patch b/vhost_gen/patches/0001-vhost-fix-alignment-for-vhost_memory.patch deleted file mode 100644 index 96a393f17c6..00000000000 --- a/vhost_gen/patches/0001-vhost-fix-alignment-for-vhost_memory.patch +++ /dev/null @@ -1,25 +0,0 @@ -From 6e2089467372ffc9d61b0902fd237a7cdd1c6660 Mon Sep 17 00:00:00 2001 -From: Andreea Florescu -Date: Tue, 27 Feb 2018 20:25:03 +0200 -Subject: [PATCH] vhost: fix alignment for vhost_memory - -Signed-off-by: Andreea Florescu ---- - vhost_gen/src/vhost.rs | 1 + - 1 file changed, 1 insertion(+) - -diff --git a/vhost_gen/src/vhost.rs b/vhost_gen/src/vhost.rs -index 7d1be6b..c6f3fdc 100644 ---- a/vhost_gen/src/vhost.rs -+++ b/vhost_gen/src/vhost.rs -@@ -500,6 +500,7 @@ pub struct vhost_memory { - pub nregions: __u32, - pub padding: __u32, - pub regions: __IncompleteArrayField, -+ __force_alignment: [u64; 0], - } - #[test] - fn bindgen_test_layout_vhost_memory() { --- -2.7.4 - diff --git a/vhost_gen/src/lib.rs b/vhost_gen/src/lib.rs deleted file mode 100644 index c51895fd829..00000000000 --- a/vhost_gen/src/lib.rs +++ /dev/null @@ -1,34 +0,0 @@ -// 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 THIRD-PARTY file. - -#![allow(clippy::all)] -#![allow(non_upper_case_globals)] -#![allow(non_camel_case_types)] -#![allow(non_snake_case)] - -#[macro_use] -extern crate sys_util; - -pub mod vhost; -pub use vhost::*; - -pub const VHOST: ::std::os::raw::c_uint = 0xaf; - -ioctl_ior_nr!(VHOST_GET_FEATURES, VHOST, 0x00, ::std::os::raw::c_ulonglong); -ioctl_iow_nr!(VHOST_SET_FEATURES, VHOST, 0x00, ::std::os::raw::c_ulonglong); -ioctl_io_nr!(VHOST_SET_OWNER, VHOST, 0x01); -ioctl_iow_nr!(VHOST_SET_MEM_TABLE, VHOST, 0x03, vhost_memory); -ioctl_iow_nr!(VHOST_SET_VRING_NUM, VHOST, 0x10, vhost_vring_state); -ioctl_iow_nr!(VHOST_SET_VRING_ADDR, VHOST, 0x11, vhost_vring_addr); -ioctl_iow_nr!(VHOST_SET_VRING_BASE, VHOST, 0x12, vhost_vring_state); -ioctl_iowr_nr!(VHOST_GET_VRING_BASE, VHOST, 0x12, vhost_vring_state); -ioctl_iow_nr!(VHOST_SET_VRING_KICK, VHOST, 0x20, vhost_vring_file); -ioctl_iow_nr!(VHOST_SET_VRING_CALL, VHOST, 0x21, vhost_vring_file); -ioctl_iow_nr!( - VHOST_VSOCK_SET_GUEST_CID, - VHOST, - 0x60, - ::std::os::raw::c_ulonglong -); -ioctl_iow_nr!(VHOST_VSOCK_SET_RUNNING, VHOST, 0x61, ::std::os::raw::c_int); diff --git a/vhost_gen/src/vhost.rs b/vhost_gen/src/vhost.rs deleted file mode 100644 index c77c6ddf94d..00000000000 --- a/vhost_gen/src/vhost.rs +++ /dev/null @@ -1,546 +0,0 @@ -// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -/* automatically generated by rust-bindgen */ - -#[repr(C)] -#[derive(Default)] -pub struct __IncompleteArrayField(::std::marker::PhantomData); -impl __IncompleteArrayField { - #[inline] - pub fn new() -> Self { - __IncompleteArrayField(::std::marker::PhantomData) - } - #[inline] - pub unsafe fn as_ptr(&self) -> *const T { - ::std::mem::transmute(self) - } - #[inline] - pub unsafe fn as_mut_ptr(&mut self) -> *mut T { - ::std::mem::transmute(self) - } - #[inline] - pub unsafe fn as_slice(&self, len: usize) -> &[T] { - ::std::slice::from_raw_parts(self.as_ptr(), len) - } - #[inline] - pub unsafe fn as_mut_slice(&mut self, len: usize) -> &mut [T] { - ::std::slice::from_raw_parts_mut(self.as_mut_ptr(), len) - } -} -impl ::std::fmt::Debug for __IncompleteArrayField { - fn fmt(&self, fmt: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { - fmt.write_str("__IncompleteArrayField") - } -} -impl ::std::clone::Clone for __IncompleteArrayField { - #[inline] - fn clone(&self) -> Self { - Self::new() - } -} -impl ::std::marker::Copy for __IncompleteArrayField {} -pub const __BITS_PER_LONG: ::std::os::raw::c_uint = 64; -pub const BITS_PER_LONG: ::std::os::raw::c_uint = 32; -pub const BITS_PER_LONG_LONG: ::std::os::raw::c_uint = 64; -pub const __FD_SETSIZE: ::std::os::raw::c_uint = 1024; -pub const VHOST_VRING_F_LOG: ::std::os::raw::c_uint = 0; -pub const VHOST_ACCESS_RO: ::std::os::raw::c_uint = 1; -pub const VHOST_ACCESS_WO: ::std::os::raw::c_uint = 2; -pub const VHOST_ACCESS_RW: ::std::os::raw::c_uint = 3; -pub const VHOST_IOTLB_MISS: ::std::os::raw::c_uint = 1; -pub const VHOST_IOTLB_UPDATE: ::std::os::raw::c_uint = 2; -pub const VHOST_IOTLB_INVALIDATE: ::std::os::raw::c_uint = 3; -pub const VHOST_IOTLB_ACCESS_FAIL: ::std::os::raw::c_uint = 4; -pub const VHOST_IOTLB_MSG: ::std::os::raw::c_uint = 1; -pub const VHOST_PAGE_SIZE: ::std::os::raw::c_uint = 4096; -pub const VHOST_VIRTIO: ::std::os::raw::c_uint = 175; -pub const VHOST_VRING_LITTLE_ENDIAN: ::std::os::raw::c_uint = 0; -pub const VHOST_VRING_BIG_ENDIAN: ::std::os::raw::c_uint = 1; -pub const VHOST_F_LOG_ALL: ::std::os::raw::c_uint = 26; -pub const VHOST_NET_F_VIRTIO_NET_HDR: ::std::os::raw::c_uint = 27; -pub const VHOST_SCSI_ABI_VERSION: ::std::os::raw::c_uint = 1; -pub type __s8 = ::std::os::raw::c_schar; -pub type __u8 = ::std::os::raw::c_uchar; -pub type __s16 = ::std::os::raw::c_short; -pub type __u16 = ::std::os::raw::c_ushort; -pub type __s32 = ::std::os::raw::c_int; -pub type __u32 = ::std::os::raw::c_uint; -pub type __s64 = ::std::os::raw::c_longlong; -pub type __u64 = ::std::os::raw::c_ulonglong; -pub const false_: _bindgen_ty_1 = 0; -pub const true_: _bindgen_ty_1 = 1; -pub type _bindgen_ty_1 = ::std::os::raw::c_uint; -#[repr(C)] -#[derive(Debug, Default, Copy, Clone)] -pub struct __kernel_fd_set { - pub fds_bits: [::std::os::raw::c_ulong; 16usize], -} -#[test] -fn bindgen_test_layout___kernel_fd_set() { - assert_eq!( - ::std::mem::size_of::<__kernel_fd_set>(), - 128usize, - concat!("Size of: ", stringify!(__kernel_fd_set)) - ); - assert_eq!( - ::std::mem::align_of::<__kernel_fd_set>(), - 8usize, - concat!("Alignment of ", stringify!(__kernel_fd_set)) - ); -} -pub type __kernel_sighandler_t = - ::std::option::Option; -pub type __kernel_key_t = ::std::os::raw::c_int; -pub type __kernel_mqd_t = ::std::os::raw::c_int; -pub type __kernel_old_uid_t = ::std::os::raw::c_ushort; -pub type __kernel_old_gid_t = ::std::os::raw::c_ushort; -pub type __kernel_old_dev_t = ::std::os::raw::c_ulong; -pub type __kernel_long_t = ::std::os::raw::c_long; -pub type __kernel_ulong_t = ::std::os::raw::c_ulong; -pub type __kernel_ino_t = __kernel_ulong_t; -pub type __kernel_mode_t = ::std::os::raw::c_uint; -pub type __kernel_pid_t = ::std::os::raw::c_int; -pub type __kernel_ipc_pid_t = ::std::os::raw::c_int; -pub type __kernel_uid_t = ::std::os::raw::c_uint; -pub type __kernel_gid_t = ::std::os::raw::c_uint; -pub type __kernel_suseconds_t = __kernel_long_t; -pub type __kernel_daddr_t = ::std::os::raw::c_int; -pub type __kernel_uid32_t = ::std::os::raw::c_uint; -pub type __kernel_gid32_t = ::std::os::raw::c_uint; -pub type __kernel_size_t = __kernel_ulong_t; -pub type __kernel_ssize_t = __kernel_long_t; -pub type __kernel_ptrdiff_t = __kernel_long_t; -#[repr(C)] -#[derive(Debug, Default, Copy, Clone)] -pub struct __kernel_fsid_t { - pub val: [::std::os::raw::c_int; 2usize], -} -#[test] -fn bindgen_test_layout___kernel_fsid_t() { - assert_eq!( - ::std::mem::size_of::<__kernel_fsid_t>(), - 8usize, - concat!("Size of: ", stringify!(__kernel_fsid_t)) - ); - assert_eq!( - ::std::mem::align_of::<__kernel_fsid_t>(), - 4usize, - concat!("Alignment of ", stringify!(__kernel_fsid_t)) - ); -} -pub type __kernel_off_t = __kernel_long_t; -pub type __kernel_loff_t = ::std::os::raw::c_longlong; -pub type __kernel_time_t = __kernel_long_t; -pub type __kernel_clock_t = __kernel_long_t; -pub type __kernel_timer_t = ::std::os::raw::c_int; -pub type __kernel_clockid_t = ::std::os::raw::c_int; -pub type __kernel_caddr_t = *mut ::std::os::raw::c_char; -pub type __kernel_uid16_t = ::std::os::raw::c_ushort; -pub type __kernel_gid16_t = ::std::os::raw::c_ushort; -pub type __le16 = __u16; -pub type __be16 = __u16; -pub type __le32 = __u32; -pub type __be32 = __u32; -pub type __le64 = __u64; -pub type __be64 = __u64; -pub type __sum16 = __u16; -pub type __wsum = __u32; -pub type __kernel_dev_t = __u32; -pub type fd_set = __kernel_fd_set; -pub type dev_t = __kernel_dev_t; -pub type ino_t = __kernel_ino_t; -pub type mode_t = __kernel_mode_t; -pub type umode_t = ::std::os::raw::c_ushort; -pub type nlink_t = __u32; -pub type off_t = __kernel_off_t; -pub type pid_t = __kernel_pid_t; -pub type daddr_t = __kernel_daddr_t; -pub type key_t = __kernel_key_t; -pub type suseconds_t = __kernel_suseconds_t; -pub type timer_t = __kernel_timer_t; -pub type clockid_t = __kernel_clockid_t; -pub type mqd_t = __kernel_mqd_t; -pub type bool_ = bool; -pub type uid_t = __kernel_uid32_t; -pub type gid_t = __kernel_gid32_t; -pub type uid16_t = __kernel_uid16_t; -pub type gid16_t = __kernel_gid16_t; -pub type loff_t = __kernel_loff_t; -pub type time_t = __kernel_time_t; -pub type clock_t = __kernel_clock_t; -pub type caddr_t = __kernel_caddr_t; -pub type u_char = ::std::os::raw::c_uchar; -pub type u_short = ::std::os::raw::c_ushort; -pub type u_int = ::std::os::raw::c_uint; -pub type u_long = ::std::os::raw::c_ulong; -pub type unchar = ::std::os::raw::c_uchar; -pub type ushort = ::std::os::raw::c_ushort; -pub type uint = ::std::os::raw::c_uint; -pub type ulong = ::std::os::raw::c_ulong; -pub type u_int8_t = __u8; -pub type u_int16_t = __u16; -pub type u_int32_t = __u32; -pub type u_int64_t = __u64; -pub type sector_t = ::std::os::raw::c_ulong; -pub type blkcnt_t = ::std::os::raw::c_ulong; -pub type dma_addr_t = u32; -pub type gfp_t = ::std::os::raw::c_uint; -pub type fmode_t = ::std::os::raw::c_uint; -pub type phys_addr_t = u32; -pub type resource_size_t = phys_addr_t; -pub type irq_hw_number_t = ::std::os::raw::c_ulong; -#[repr(C)] -#[derive(Debug, Default, Copy, Clone)] -pub struct atomic_t { - pub counter: ::std::os::raw::c_int, -} -#[test] -fn bindgen_test_layout_atomic_t() { - assert_eq!( - ::std::mem::size_of::(), - 4usize, - concat!("Size of: ", stringify!(atomic_t)) - ); - assert_eq!( - ::std::mem::align_of::(), - 4usize, - concat!("Alignment of ", stringify!(atomic_t)) - ); -} -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct list_head { - pub next: *mut list_head, - pub prev: *mut list_head, -} -#[test] -fn bindgen_test_layout_list_head() { - assert_eq!( - ::std::mem::size_of::(), - 16usize, - concat!("Size of: ", stringify!(list_head)) - ); - assert_eq!( - ::std::mem::align_of::(), - 8usize, - concat!("Alignment of ", stringify!(list_head)) - ); -} -impl Default for list_head { - fn default() -> Self { - unsafe { ::std::mem::zeroed() } - } -} -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct hlist_head { - pub first: *mut hlist_node, -} -#[test] -fn bindgen_test_layout_hlist_head() { - assert_eq!( - ::std::mem::size_of::(), - 8usize, - concat!("Size of: ", stringify!(hlist_head)) - ); - assert_eq!( - ::std::mem::align_of::(), - 8usize, - concat!("Alignment of ", stringify!(hlist_head)) - ); -} -impl Default for hlist_head { - fn default() -> Self { - unsafe { ::std::mem::zeroed() } - } -} -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct hlist_node { - pub next: *mut hlist_node, - pub pprev: *mut *mut hlist_node, -} -#[test] -fn bindgen_test_layout_hlist_node() { - assert_eq!( - ::std::mem::size_of::(), - 16usize, - concat!("Size of: ", stringify!(hlist_node)) - ); - assert_eq!( - ::std::mem::align_of::(), - 8usize, - concat!("Alignment of ", stringify!(hlist_node)) - ); -} -impl Default for hlist_node { - fn default() -> Self { - unsafe { ::std::mem::zeroed() } - } -} -#[repr(C)] -#[derive(Debug, Default, Copy, Clone)] -pub struct ustat { - pub f_tfree: __kernel_daddr_t, - pub f_tinode: __kernel_ino_t, - pub f_fname: [::std::os::raw::c_char; 6usize], - pub f_fpack: [::std::os::raw::c_char; 6usize], -} -#[test] -fn bindgen_test_layout_ustat() { - assert_eq!( - ::std::mem::size_of::(), - 32usize, - concat!("Size of: ", stringify!(ustat)) - ); - assert_eq!( - ::std::mem::align_of::(), - 8usize, - concat!("Alignment of ", stringify!(ustat)) - ); -} -/// struct callback_head - callback structure for use with RCU and task_work -/// @next: next update requests in a list -/// @func: actual update function to call after the grace period. -/// -/// The struct is aligned to size of pointer. On most architectures it happens -/// naturally due ABI requirements, but some architectures (like CRIS) have -/// weird ABI and we need to ask it explicitly. -/// -/// The alignment is required to guarantee that bit 0 of @next will be -/// clear under normal conditions -- as long as we use call_rcu(), -/// call_rcu_bh(), call_rcu_sched(), or call_srcu() to queue callback. -/// -/// This guarantee is important for few reasons: -/// - future call_rcu_lazy() will make use of lower bits in the pointer; -/// - the structure shares storage spacer in struct page with @compound_head, -/// which encode PageTail() in bit 0. The guarantee is needed to avoid -/// false-positive PageTail(). -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct callback_head { - pub next: *mut callback_head, - pub func: ::std::option::Option, -} -#[test] -fn bindgen_test_layout_callback_head() { - assert_eq!( - ::std::mem::size_of::(), - 16usize, - concat!("Size of: ", stringify!(callback_head)) - ); - assert_eq!( - ::std::mem::align_of::(), - 8usize, - concat!("Alignment of ", stringify!(callback_head)) - ); -} -impl Default for callback_head { - fn default() -> Self { - unsafe { ::std::mem::zeroed() } - } -} -pub type rcu_callback_t = ::std::option::Option; -pub type call_rcu_func_t = - ::std::option::Option; -#[repr(C)] -#[derive(Debug, Default, Copy, Clone)] -pub struct vhost_vring_state { - pub index: ::std::os::raw::c_uint, - pub num: ::std::os::raw::c_uint, -} -#[test] -fn bindgen_test_layout_vhost_vring_state() { - assert_eq!( - ::std::mem::size_of::(), - 8usize, - concat!("Size of: ", stringify!(vhost_vring_state)) - ); - assert_eq!( - ::std::mem::align_of::(), - 4usize, - concat!("Alignment of ", stringify!(vhost_vring_state)) - ); -} -#[repr(C)] -#[derive(Debug, Default, Copy, Clone)] -pub struct vhost_vring_file { - pub index: ::std::os::raw::c_uint, - pub fd: ::std::os::raw::c_int, -} -#[test] -fn bindgen_test_layout_vhost_vring_file() { - assert_eq!( - ::std::mem::size_of::(), - 8usize, - concat!("Size of: ", stringify!(vhost_vring_file)) - ); - assert_eq!( - ::std::mem::align_of::(), - 4usize, - concat!("Alignment of ", stringify!(vhost_vring_file)) - ); -} -#[repr(C)] -#[derive(Debug, Default, Copy, Clone)] -pub struct vhost_vring_addr { - pub index: ::std::os::raw::c_uint, - pub flags: ::std::os::raw::c_uint, - pub desc_user_addr: __u64, - pub used_user_addr: __u64, - pub avail_user_addr: __u64, - pub log_guest_addr: __u64, -} -#[test] -fn bindgen_test_layout_vhost_vring_addr() { - assert_eq!( - ::std::mem::size_of::(), - 40usize, - concat!("Size of: ", stringify!(vhost_vring_addr)) - ); - assert_eq!( - ::std::mem::align_of::(), - 8usize, - concat!("Alignment of ", stringify!(vhost_vring_addr)) - ); -} -#[repr(C)] -#[derive(Debug, Default, Copy, Clone)] -pub struct vhost_iotlb_msg { - pub iova: __u64, - pub size: __u64, - pub uaddr: __u64, - pub perm: __u8, - pub type_: __u8, -} -#[test] -fn bindgen_test_layout_vhost_iotlb_msg() { - assert_eq!( - ::std::mem::size_of::(), - 32usize, - concat!("Size of: ", stringify!(vhost_iotlb_msg)) - ); - assert_eq!( - ::std::mem::align_of::(), - 8usize, - concat!("Alignment of ", stringify!(vhost_iotlb_msg)) - ); -} -#[repr(C)] -#[derive(Copy, Clone)] -pub struct vhost_msg { - pub type_: ::std::os::raw::c_int, - pub __bindgen_anon_1: vhost_msg__bindgen_ty_1, -} -#[repr(C)] -#[derive(Copy, Clone)] -pub union vhost_msg__bindgen_ty_1 { - pub iotlb: vhost_iotlb_msg, - pub padding: [__u8; 64usize], - _bindgen_union_align: [u64; 8usize], -} -#[test] -fn bindgen_test_layout_vhost_msg__bindgen_ty_1() { - assert_eq!( - ::std::mem::size_of::(), - 64usize, - concat!("Size of: ", stringify!(vhost_msg__bindgen_ty_1)) - ); - assert_eq!( - ::std::mem::align_of::(), - 8usize, - concat!("Alignment of ", stringify!(vhost_msg__bindgen_ty_1)) - ); -} -impl Default for vhost_msg__bindgen_ty_1 { - fn default() -> Self { - unsafe { ::std::mem::zeroed() } - } -} -#[test] -fn bindgen_test_layout_vhost_msg() { - assert_eq!( - ::std::mem::size_of::(), - 72usize, - concat!("Size of: ", stringify!(vhost_msg)) - ); - assert_eq!( - ::std::mem::align_of::(), - 8usize, - concat!("Alignment of ", stringify!(vhost_msg)) - ); -} -impl Default for vhost_msg { - fn default() -> Self { - unsafe { ::std::mem::zeroed() } - } -} -#[repr(C)] -#[derive(Debug, Default, Copy, Clone)] -pub struct vhost_memory_region { - pub guest_phys_addr: __u64, - pub memory_size: __u64, - pub userspace_addr: __u64, - pub flags_padding: __u64, -} -#[test] -fn bindgen_test_layout_vhost_memory_region() { - assert_eq!( - ::std::mem::size_of::(), - 32usize, - concat!("Size of: ", stringify!(vhost_memory_region)) - ); - assert_eq!( - ::std::mem::align_of::(), - 8usize, - concat!("Alignment of ", stringify!(vhost_memory_region)) - ); -} -#[repr(C)] -#[derive(Debug, Default)] -pub struct vhost_memory { - pub nregions: __u32, - pub padding: __u32, - pub regions: __IncompleteArrayField, - __force_alignment: [u64; 0], -} -#[test] -fn bindgen_test_layout_vhost_memory() { - assert_eq!( - ::std::mem::size_of::(), - 8usize, - concat!("Size of: ", stringify!(vhost_memory)) - ); - assert_eq!( - ::std::mem::align_of::(), - 8usize, - concat!("Alignment of ", stringify!(vhost_memory)) - ); -} -#[repr(C)] -#[derive(Copy, Clone)] -pub struct vhost_scsi_target { - pub abi_version: ::std::os::raw::c_int, - pub vhost_wwpn: [::std::os::raw::c_char; 224usize], - pub vhost_tpgt: ::std::os::raw::c_ushort, - pub reserved: ::std::os::raw::c_ushort, -} -#[test] -fn bindgen_test_layout_vhost_scsi_target() { - assert_eq!( - ::std::mem::size_of::(), - 232usize, - concat!("Size of: ", stringify!(vhost_scsi_target)) - ); - assert_eq!( - ::std::mem::align_of::(), - 4usize, - concat!("Alignment of ", stringify!(vhost_scsi_target)) - ); -} -impl Default for vhost_scsi_target { - fn default() -> Self { - unsafe { ::std::mem::zeroed() } - } -} diff --git a/vmm/Cargo.toml b/vmm/Cargo.toml index 11386c83933..e0c63c5667e 100644 --- a/vmm/Cargo.toml +++ b/vmm/Cargo.toml @@ -31,7 +31,3 @@ cpuid = { path = "../cpuid" } [dev-dependencies] tempfile = ">=3.0.2" - -[features] -vsock = ["devices/vsock"] - diff --git a/vmm/src/default_syscalls/mod.rs b/vmm/src/default_syscalls/mod.rs index bac27a0dc04..b65355bc987 100644 --- a/vmm/src/default_syscalls/mod.rs +++ b/vmm/src/default_syscalls/mod.rs @@ -83,23 +83,7 @@ const TUNSETIFF: u64 = 0x4004_54ca; const TUNSETOFFLOAD: u64 = 0x4004_54d0; const TUNSETVNETHDRSZ: u64 = 0x4004_54d8; -#[cfg(feature = "vsock")] -mod vsock_ioctls { - pub const VHOST_GET_FEATURES: u64 = 0x8008_af00; - pub const VHOST_SET_FEATURES: u64 = 0x4008_af00; - pub const VHOST_SET_OWNER: u64 = 0x0000_af01; - pub const VHOST_SET_MEM_TABLE: u64 = 0x4008_af03; - pub const VHOST_SET_VRING_NUM: u64 = 0x4008_af10; - pub const VHOST_SET_VRING_ADDR: u64 = 0x4028_af11; - pub const VHOST_SET_VRING_BASE: u64 = 0x4008_af12; - pub const VHOST_GET_VRING_BASE: u64 = 0xc008_af12; - pub const VHOST_SET_VRING_KICK: u64 = 0x4008_af20; - pub const VHOST_SET_VRING_CALL: u64 = 0x4008_af21; - pub const VHOST_VSOCK_SET_GUEST_CID: u64 = 0x4008_af60; - pub const VHOST_VSOCK_SET_RUNNING: u64 = 0x4004_af61; -} - -fn create_common_ioctl_seccomp_rule() -> Result, Error> { +fn create_ioctl_seccomp_rule() -> Result, Error> { Ok(or![ and![Cond::new(1, Eq, TCSETS)?], and![Cond::new(1, Eq, TCGETS)?], @@ -134,35 +118,6 @@ fn create_common_ioctl_seccomp_rule() -> Result, Error> { ]) } -#[cfg(feature = "vsock")] -fn create_vsock_ioctl_seccomp_rule() -> Result, Error> { - Ok(or![ - and![Cond::new(1, Eq, vsock_ioctls::VHOST_GET_FEATURES,)?], - and![Cond::new(1, Eq, vsock_ioctls::VHOST_SET_FEATURES,)?], - and![Cond::new(1, Eq, vsock_ioctls::VHOST_SET_OWNER,)?], - and![Cond::new(1, Eq, vsock_ioctls::VHOST_SET_MEM_TABLE,)?], - and![Cond::new(1, Eq, vsock_ioctls::VHOST_SET_VRING_NUM,)?], - and![Cond::new(1, Eq, vsock_ioctls::VHOST_SET_VRING_ADDR,)?], - and![Cond::new(1, Eq, vsock_ioctls::VHOST_SET_VRING_BASE,)?], - and![Cond::new(1, Eq, vsock_ioctls::VHOST_GET_VRING_BASE,)?], - and![Cond::new(1, Eq, vsock_ioctls::VHOST_SET_VRING_KICK,)?], - and![Cond::new(1, Eq, vsock_ioctls::VHOST_SET_VRING_CALL,)?], - and![Cond::new(1, Eq, vsock_ioctls::VHOST_VSOCK_SET_GUEST_CID,)?], - and![Cond::new(1, Eq, vsock_ioctls::VHOST_VSOCK_SET_RUNNING,)?], - ]) -} - -fn create_ioctl_seccomp_rule() -> Result, Error> { - #[cfg(feature = "vsock")] - { - let mut rule = create_common_ioctl_seccomp_rule()?; - rule.append(&mut create_vsock_ioctl_seccomp_rule()?); - Ok(rule) - } - #[cfg(not(feature = "vsock"))] - Ok(create_common_ioctl_seccomp_rule()?) -} - #[cfg(test)] #[cfg(target_env = "musl")] mod tests { diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs index 38e4710972c..59ae095b5e0 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -68,8 +68,7 @@ use device_manager::mmio::MMIODeviceInfo; use device_manager::mmio::MMIODeviceManager; use devices::legacy::I8042DeviceError; use devices::virtio; -#[cfg(feature = "vsock")] -use devices::virtio::vhost::{handle::VHOST_EVENTS_COUNT, TYPE_VSOCK}; +use devices::virtio::vsock::{TYPE_VSOCK, VSOCK_EVENTS_COUNT}; use devices::virtio::EpollConfigConstructor; use devices::virtio::{BLOCK_EVENTS_COUNT, TYPE_BLOCK}; use devices::virtio::{NET_EVENTS_COUNT, TYPE_NET}; @@ -95,7 +94,6 @@ use vmm_config::net::{ NetworkInterfaceConfig, NetworkInterfaceConfigs, NetworkInterfaceError, NetworkInterfaceUpdateConfig, }; -#[cfg(feature = "vsock")] use vmm_config::vsock::{VsockDeviceConfig, VsockDeviceConfigs, VsockError}; use vstate::{Vcpu, Vm}; @@ -224,7 +222,6 @@ pub enum VmmActionError { /// The action `SendCtrlAltDel` failed. Details are provided by the device-specific error /// `I8042DeviceError`. SendCtrlAltDel(ErrorKind, I8042DeviceError), - #[cfg(feature = "vsock")] /// The action `insert_vsock_device` failed either because of bad user input (`ErrorKind::User`) /// or an internal error (`ErrorKind::Internal`). VsockConfig(ErrorKind, VsockError), @@ -307,10 +304,9 @@ impl std::convert::From for VmmActionError { let kind = match e { // User errors. - #[cfg(feature = "vsock")] - CreateVsockDevice(_) => ErrorKind::User, CreateBlockDevice(_) | CreateNetDevice(_) + | CreateVsockDevice | KernelCmdline(_) | KernelLoader(_) | MicroVMAlreadyRunning @@ -319,8 +315,6 @@ impl std::convert::From for VmmActionError { | OpenBlockDevice(_) | VcpusNotConfigured => ErrorKind::User, // Internal errors. - #[cfg(feature = "vsock")] - RegisterVsockDevice(_) => ErrorKind::Internal, ConfigureSystem(_) | ConfigureVm(_) | CreateRateLimiter(_) @@ -332,6 +326,7 @@ impl std::convert::From for VmmActionError { | RegisterEvent | RegisterMMIODevice(_) | RegisterNetDevice(_) + | RegisterVsockDevice(_) | SeccompFilters(_) | Vcpu(_) | VcpuConfigure(_) @@ -359,7 +354,6 @@ impl VmmActionError { NetworkConfig(ref kind, _) => kind, StartMicrovm(ref kind, _) => kind, SendCtrlAltDel(ref kind, _) => kind, - #[cfg(feature = "vsock")] VsockConfig(ref kind, _) => kind, } } @@ -377,7 +371,6 @@ impl Display for VmmActionError { NetworkConfig(_, ref err) => err, StartMicrovm(_, ref err) => err, SendCtrlAltDel(_, ref err) => err, - #[cfg(feature = "vsock")] VsockConfig(_, ref err) => err, }; @@ -410,7 +403,6 @@ pub enum VmmAction { /// `NetworkInterfaceConfig` as input. This action can only be called before the microVM has /// booted. The response is sent using the `OutcomeSender`. InsertNetworkDevice(NetworkInterfaceConfig, OutcomeSender), - #[cfg(feature = "vsock")] /// Add a new vsock device or update one that already exists using the /// `VsockDeviceConfig` as input. This action can only be called before the microVM has /// booted. The response is sent using the `OutcomeSender`. @@ -776,7 +768,6 @@ struct Vmm { // This is necessary because we want the root to always be mounted on /dev/vda. block_device_configs: BlockDeviceConfigs, network_interface_configs: NetworkInterfaceConfigs, - #[cfg(feature = "vsock")] vsock_device_configs: VsockDeviceConfigs, epoll_context: EpollContext, @@ -832,7 +823,6 @@ impl Vmm { legacy_device_manager: LegacyDeviceManager::new().map_err(Error::CreateLegacyDevice)?, block_device_configs, network_interface_configs: NetworkInterfaceConfigs::new(), - #[cfg(feature = "vsock")] vsock_device_configs: VsockDeviceConfigs::new(), epoll_context, api_event_fd, @@ -1003,41 +993,42 @@ impl Vmm { Ok(()) } - #[cfg(feature = "vsock")] - fn attach_vsock_devices( - &mut self, - guest_mem: &GuestMemory, - ) -> std::result::Result<(), StartMicrovmError> { - use StartMicrovmError::*; - - let kernel_config = self.kernel_config.as_mut().ok_or(MissingKernelConfig)?; - + fn attach_vsock_devices(&mut self) -> std::result::Result<(), StartMicrovmError> { + let kernel_config = self + .kernel_config + .as_mut() + .ok_or(StartMicrovmError::MissingKernelConfig)?; // `unwrap` is suitable for this context since this should be called only after the // device manager has been initialized. let device_manager = self.mmio_device_manager.as_mut().unwrap(); for cfg in self.vsock_device_configs.iter() { + let backend = devices::virtio::vsock::VsockUnixBackend::new( + u64::from(cfg.guest_cid), + cfg.uds_path.clone(), + ) + .map_err(|_| StartMicrovmError::CreateVsockDevice)?; + let epoll_config = self.epoll_context.allocate_tokens_for_virtio_device( TYPE_VSOCK, - &cfg.id, - VHOST_EVENTS_COUNT, + &cfg.vsock_id, + VSOCK_EVENTS_COUNT, ); - let vsock_box = Box::new( - devices::virtio::Vsock::new(u64::from(cfg.guest_cid), guest_mem, epoll_config) - .map_err(CreateVsockDevice)?, + devices::virtio::Vsock::new(u64::from(cfg.guest_cid), epoll_config, backend) + .or(Err(StartMicrovmError::CreateVsockDevice))?, ); - device_manager .register_virtio_device( self.vm.get_fd(), vsock_box, &mut kernel_config.cmdline, TYPE_VSOCK, - &cfg.id, + &cfg.vsock_id, ) - .map_err(RegisterVsockDevice)?; + .map_err(StartMicrovmError::RegisterVsockDevice)?; } + Ok(()) } @@ -1129,16 +1120,7 @@ impl Vmm { self.attach_block_devices()?; self.attach_net_devices()?; - #[cfg(feature = "vsock")] - { - let guest_mem = self - .guest_memory - .clone() - .ok_or(StartMicrovmError::GuestMemory( - memory_model::GuestMemoryError::MemoryNotInitialized, - ))?; - self.attach_vsock_devices(&guest_mem)?; - } + self.attach_vsock_devices()?; Ok(()) } @@ -1776,7 +1758,6 @@ impl Vmm { Ok(VmmData::Empty) } - #[cfg(feature = "vsock")] fn insert_vsock_device( &mut self, body: VsockDeviceConfig, @@ -1976,7 +1957,6 @@ impl Vmm { InsertNetworkDevice(netif_body, sender) => { Vmm::send_response(self.insert_net_device(netif_body), sender); } - #[cfg(feature = "vsock")] InsertVsockDevice(vsock_cfg, sender) => { Vmm::send_response(self.insert_vsock_device(vsock_cfg), sender); } @@ -3455,6 +3435,7 @@ mod tests { } #[test] + #[allow(clippy::cognitive_complexity)] fn test_start_microvm_error_conversion_cl() { // Test `StartMicrovmError` conversion #[cfg(target_arch = "x86_64")] @@ -3490,11 +3471,8 @@ mod tests { )), ErrorKind::Internal ); - #[cfg(feature = "vsock")] assert_eq!( - error_kind(StartMicrovmError::CreateVsockDevice( - devices::virtio::vhost::Error::PollError(io::Error::from_raw_os_error(0)) - )), + error_kind(StartMicrovmError::CreateVsockDevice), ErrorKind::User ); assert_eq!( @@ -3580,7 +3558,6 @@ mod tests { )), ErrorKind::Internal ); - #[cfg(feature = "vsock")] assert_eq!( error_kind(StartMicrovmError::RegisterVsockDevice( device_manager::mmio::Error::IrqsExhausted @@ -3616,6 +3593,7 @@ mod tests { } #[test] + #[allow(clippy::cognitive_complexity)] fn test_error_messages() { // Enum `Error` @@ -3766,7 +3744,6 @@ mod tests { .kind(), &ErrorKind::User ); - #[cfg(feature = "vsock")] assert_eq!( format!( "{:?}", diff --git a/vmm/src/vmm_config/instance_info.rs b/vmm/src/vmm_config/instance_info.rs index b68120ad730..73ce62cdab5 100644 --- a/vmm/src/vmm_config/instance_info.rs +++ b/vmm/src/vmm_config/instance_info.rs @@ -58,9 +58,8 @@ pub enum StartMicrovmError { CreateNetDevice(devices::virtio::Error), /// Failed to create a `RateLimiter` object. CreateRateLimiter(std::io::Error), - #[cfg(feature = "vsock")] - /// Creating a vsock device can only fail if the /dev/vhost-vsock device cannot be open. - CreateVsockDevice(devices::virtio::vhost::Error), + /// Failed to create the vsock device. + CreateVsockDevice, /// The device manager was not configured. DeviceManager, /// Cannot read from an Event file descriptor. @@ -91,7 +90,6 @@ pub enum StartMicrovmError { RegisterMMIODevice(device_manager::mmio::Error), /// Cannot initialize a MMIO Network Device or add a device to the MMIO Bus. RegisterNetDevice(device_manager::mmio::Error), - #[cfg(feature = "vsock")] /// Cannot initialize a MMIO Vsock Device or add a device to the MMIO Bus. RegisterVsockDevice(device_manager::mmio::Error), /// Cannot build seccomp filters. @@ -137,13 +135,7 @@ impl Display for StartMicrovmError { err ), CreateRateLimiter(ref err) => write!(f, "Cannot create RateLimiter: {}", err), - #[cfg(feature = "vsock")] - CreateVsockDevice(ref err) => { - let mut err_msg = format!("{:?}", err); - err_msg = err_msg.replace("\"", ""); - - write!(f, "Cannot create vsock device. {}", err_msg) - } + CreateVsockDevice => write!(f, "Cannot create vsock device."), CreateNetDevice(ref err) => { let mut err_msg = format!("{:?}", err); err_msg = err_msg.replace("\"", ""); @@ -217,7 +209,6 @@ impl Display for StartMicrovmError { err_msg ) } - #[cfg(feature = "vsock")] RegisterVsockDevice(ref err) => { let mut err_msg = format!("{}", err); err_msg = err_msg.replace("\"", ""); diff --git a/vmm/src/vmm_config/mod.rs b/vmm/src/vmm_config/mod.rs index b898f437989..8f036cc2f4a 100644 --- a/vmm/src/vmm_config/mod.rs +++ b/vmm/src/vmm_config/mod.rs @@ -16,7 +16,6 @@ pub mod logger; pub mod machine_config; /// Wrapper for configuring the network devices attached to the microVM. pub mod net; -#[cfg(feature = "vsock")] /// Wrapper for configuring the vsock devices attached to the microVM. pub mod vsock; diff --git a/vmm/src/vmm_config/vsock.rs b/vmm/src/vmm_config/vsock.rs index e17db750089..1ae9f418ac5 100644 --- a/vmm/src/vmm_config/vsock.rs +++ b/vmm/src/vmm_config/vsock.rs @@ -10,9 +10,11 @@ use std::result; #[serde(deny_unknown_fields)] pub struct VsockDeviceConfig { /// ID of the vsock device. - pub id: String, + pub vsock_id: String, /// A 32-bit Context Identifier (CID) used to identify the guest. pub guest_cid: u32, + /// Path to local unix socket. + pub uds_path: String, } /// Errors associated with `VsockDeviceConfig`. @@ -71,7 +73,7 @@ impl VsockDeviceConfigs { match self .configs .iter() - .position(|cfg_from_list| cfg_from_list.id.as_str() == cfg.id.as_str()) + .position(|cfg_from_list| cfg_from_list.vsock_id.as_str() == cfg.vsock_id.as_str()) { Some(index) => self.configs[index] = cfg, None => self.configs.push(cfg), @@ -85,3 +87,31 @@ impl VsockDeviceConfigs { self.configs.iter() } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_vsock_device_config() { + const CID: u32 = 52; + let cfg = VsockDeviceConfig { + vsock_id: String::from("vsock"), + guest_cid: CID, + uds_path: String::from("/tmp/vsock.sock"), + }; + let mut cfg_list = VsockDeviceConfigs::new(); + + cfg_list.add(cfg.clone()).unwrap(); + assert!(cfg_list.contains_cid(CID)); + + assert_eq!( + format!("{}", cfg_list.add(cfg.clone()).err().unwrap()), + format!("The guest CID {} is already in use.", CID), + ); + + let mut cfg2 = cfg.clone(); + cfg2.guest_cid = CID + 1; + assert!(cfg_list.add(cfg2).is_ok()); + } +}