Skip to content

Commit 50343ab

Browse files
committed
Actually make use of bhyve and viona API versions
Both bhyve (the kernel vmm portion) and viona expose version information as it relates to their respective ioctl APIs. Although there are few (read: none) guarantees about one can depend on when it comes to inferring compatibility from said versions, it does provide some modicum of protection against weird behavior when expectations of those interfaces differ between propolis and the underlying OS.
1 parent e874e49 commit 50343ab

File tree

14 files changed

+194
-9
lines changed

14 files changed

+194
-9
lines changed

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

bin/propolis-server/src/main.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
#![cfg_attr(usdt_need_asm, feature(asm))]
33
#![cfg_attr(all(target_os = "macos", usdt_need_asm_sym), feature(asm_sym))]
44

5-
use anyhow::anyhow;
5+
use anyhow::{anyhow, Context};
66
use clap::Parser;
77
use dropshot::{
88
ConfigDropshot, ConfigLogging, ConfigLoggingLevel, HttpServerStarter,
@@ -86,6 +86,9 @@ async fn main() -> anyhow::Result<()> {
8686
|error| anyhow!("failed to create logger: {}", error),
8787
)?;
8888

89+
// Check that devices conform to expected API version
90+
propolis::api_version::check().context("API version checks")?;
91+
8992
let vnc_server = setup_vnc(&log, vnc_addr);
9093
let vnc_server_hdl = vnc_server.clone();
9194
let use_reservoir = config::reservoir_decide(&log);

bin/propolis-standalone/src/main.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -863,6 +863,31 @@ fn setup_instance(
863863
Ok((inst, com1_sock))
864864
}
865865

866+
/// Check bhyve and viona API versions, squawking if they do not meet
867+
/// expectations, but ultimately still allowing forward progress since
868+
/// propolis-standalone lives in the Thunderdome.
869+
fn api_version_checks(log: &slog::Logger) -> std::io::Result<()> {
870+
match api_version::check() {
871+
Err(api_version::Error::Io(e)) => {
872+
// IO errors _are_ fatal
873+
Err(e)
874+
}
875+
Err(api_version::Error::Mismatch(comp, act, exp)) => {
876+
// Make noise about version mismatch, but soldier on and let the
877+
// user decide if they want to quit
878+
slog::error!(
879+
log,
880+
"{} API version mismatch {} != {}",
881+
comp,
882+
act,
883+
exp
884+
);
885+
Ok(())
886+
}
887+
Ok(_) => Ok(()),
888+
}
889+
}
890+
866891
#[derive(clap::Parser)]
867892
/// Propolis command-line frontend for running a VM.
868893
struct Args {
@@ -887,6 +912,9 @@ fn main() -> anyhow::Result<()> {
887912

888913
let (log, _log_async_guard) = build_log();
889914

915+
// Check that vmm and viona device version match what we expect
916+
api_version_checks(&log).context("API version checks")?;
917+
890918
// Create tokio runtime, we don't use the tokio::main macro
891919
// since we'll block in main when we call `Instance::wait_for_state`
892920
let rt =

crates/bhyve-api/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@ edition = "2018"
99
[dependencies]
1010
libc.workspace = true
1111
bhyve_api_sys.workspace = true
12+
num_enum.workspace = true

crates/bhyve-api/src/lib.rs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ use std::os::fd::*;
44
use std::os::unix::fs::OpenOptionsExt;
55
use std::path::PathBuf;
66

7+
use num_enum::IntoPrimitive;
8+
79
pub use bhyve_api_sys::*;
810

911
pub const VMM_PATH_PREFIX: &str = "/dev/vmm";
@@ -43,6 +45,16 @@ impl VmmCtlFd {
4345
unsafe { ioctl(self.as_raw_fd(), cmd, data as *mut libc::c_void) }
4446
}
4547

48+
/// Query the API version exposed by the kernel VMM.
49+
pub fn api_version(&self) -> Result<u32> {
50+
let vers = self.ioctl_usize(ioctls::VMM_INTERFACE_VERSION, 0)?;
51+
52+
// We expect and demand a positive version number from the
53+
// VMM_INTERFACE_VERSION interface.
54+
assert!(vers > 0);
55+
Ok(vers as u32)
56+
}
57+
4658
/// Check VMM ioctl command against those known to not require any
4759
/// copyin/copyout to function.
4860
const fn ioctl_usize_safe(cmd: i32) -> bool {
@@ -151,3 +163,43 @@ unsafe fn ioctl(
151163
) -> Result<i32> {
152164
Err(Error::new(ErrorKind::Other, "illumos required"))
153165
}
166+
167+
/// Convenience constants to provide some documentation on what changes have
168+
/// been introduced in the various bhyve API versions.
169+
#[repr(u32)]
170+
#[derive(IntoPrimitive)]
171+
pub enum ApiVersion {
172+
/// Revamps ioctls for administrating the VMM memory reservoir and adds
173+
/// kstat for tracking its capacity and utilization.
174+
V9 = 9,
175+
176+
/// Adds flag to enable dirty page tracking for VMs when running on hardware
177+
/// with adequate support.
178+
V8 = 8,
179+
180+
/// Adds pause/resume ioctls to assist with the ability to load or store a
181+
/// consistent snapshot of VM state
182+
V7 = 7,
183+
184+
/// Made hlt-on-exit a required CPU feature, and enabled by default in vmm
185+
V6 = 6,
186+
187+
/// Adds ability to control CPUID results for guest vCPUs
188+
V5 = 5,
189+
}
190+
impl ApiVersion {
191+
pub const fn current() -> Self {
192+
Self::V9
193+
}
194+
}
195+
196+
#[cfg(test)]
197+
mod test {
198+
use super::*;
199+
200+
#[test]
201+
fn latest_api_version() {
202+
let cur = ApiVersion::current();
203+
assert_eq!(VMM_CURRENT_INTERFACE_VERSION, cur.into());
204+
}
205+
}

crates/bhyve-api/sys/src/ioctls.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,7 @@ pub const VMM_INTERFACE_VERSION: i32 = VMMCTL_IOC_BASE | 0x04;
1313

1414
// VMM memory reservoir operations
1515
pub const VMM_RESV_QUERY: i32 = VMMCTL_IOC_BASE | 0x10;
16-
pub const VMM_RESV_ADD: i32 = VMMCTL_IOC_BASE | 0x11;
17-
pub const VMM_RESV_REMOVE: i32 = VMMCTL_IOC_BASE | 0x12;
16+
pub const VMM_RESV_SET_TARGET: i32 = VMMCTL_IOC_BASE | 0x11;
1817

1918
// Operations performed in the context of a given vCPU
2019
pub const VM_RUN: i32 = VMM_CPU_IOC_BASE | 0x01;

crates/bhyve-api/sys/src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ pub use vmm_data::*;
1010

1111
pub const VM_MAXCPU: usize = 32;
1212

13-
/// This is the VMM interface version against which bhyve_api expects to operate
13+
/// This is the VMM interface version which bhyve_api expects to operate
1414
/// against. All constants and structs defined by the crate are done so in
1515
/// terms of that specific version.
16-
pub const VMM_CURRENT_INTERFACE_VERSION: u32 = 8;
16+
pub const VMM_CURRENT_INTERFACE_VERSION: u32 = 9;

crates/viona-api/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@ edition = "2018"
99
[dependencies]
1010
libc.workspace = true
1111
viona_api_sys.workspace = true
12+
num_enum.workspace = true

crates/viona-api/src/lib.rs

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,32 @@ use std::fs::{File, OpenOptions};
22
use std::io::{Error, ErrorKind, Result};
33
use std::os::fd::*;
44

5+
use num_enum::IntoPrimitive;
6+
57
pub use viona_api_sys::*;
68

79
pub const VIONA_DEV_PATH: &str = "/dev/viona";
810

911
pub struct VionaFd(File);
1012
impl VionaFd {
13+
/// Open viona device and associate it with a given link and vmm instance,
14+
/// provided in `link_id` and `vm_fd`, respectively.
1115
pub fn new(link_id: u32, vm_fd: RawFd) -> Result<Self> {
12-
let fp =
13-
OpenOptions::new().read(true).write(true).open(VIONA_DEV_PATH)?;
14-
15-
let this = Self(fp);
16+
let this = Self::open()?;
1617

1718
let mut vna_create = vioc_create { c_linkid: link_id, c_vmfd: vm_fd };
1819
let _ = unsafe { this.ioctl(ioctls::VNA_IOC_CREATE, &mut vna_create) }?;
1920
Ok(this)
2021
}
2122

23+
/// Open viona device instance without performing any other initialization
24+
pub fn open() -> Result<Self> {
25+
let fp =
26+
OpenOptions::new().read(true).write(true).open(VIONA_DEV_PATH)?;
27+
28+
Ok(Self(fp))
29+
}
30+
2231
/// Issue ioctl against open viona instance
2332
///
2433
/// # Safety
@@ -44,6 +53,16 @@ impl VionaFd {
4453
unsafe { ioctl(self.as_raw_fd(), cmd, data as *mut libc::c_void) }
4554
}
4655

56+
/// Query the API version exposed by the kernel VMM.
57+
pub fn api_version(&self) -> Result<u32> {
58+
let vers = self.ioctl_usize(ioctls::VNA_IOC_VERSION, 0)?;
59+
60+
// We expect and demand a positive version number from the
61+
// VNA_IOC_VERSION interface.
62+
assert!(vers > 0);
63+
Ok(vers as u32)
64+
}
65+
4766
/// Check VMM ioctl command against those known to not require any
4867
/// copyin/copyout to function.
4968
const fn ioctl_usize_safe(cmd: i32) -> bool {
@@ -54,6 +73,7 @@ impl VionaFd {
5473
| ioctls::VNA_IOC_RING_KICK
5574
| ioctls::VNA_IOC_RING_PAUSE
5675
| ioctls::VNA_IOC_RING_INTR_CLR
76+
| ioctls::VNA_IOC_VERSION
5777
)
5878
}
5979
}
@@ -79,3 +99,31 @@ unsafe fn ioctl(
7999
) -> Result<i32> {
80100
Err(Error::new(ErrorKind::Other, "illumos required"))
81101
}
102+
103+
/// Convenience constants to provide some documentation on what changes have
104+
/// been introduced in the various viona API versions.
105+
#[repr(u32)]
106+
#[derive(IntoPrimitive)]
107+
pub enum ApiVersion {
108+
/// Adds support for non-vnic datalink devices
109+
V2 = 2,
110+
111+
/// Initial version available for query
112+
V1 = 1,
113+
}
114+
impl ApiVersion {
115+
pub const fn current() -> Self {
116+
Self::V2
117+
}
118+
}
119+
120+
#[cfg(test)]
121+
mod test {
122+
use super::*;
123+
124+
#[test]
125+
fn latest_api_version() {
126+
let cur = ApiVersion::current();
127+
assert_eq!(VIONA_CURRENT_INTERFACE_VERSION, cur.into());
128+
}
129+
}

crates/viona-api/sys/src/lib.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,5 +65,10 @@ mod structs {
6565
}
6666
}
6767

68+
/// This is the viona interface version which viona_api expects to operate
69+
/// against. All constants and structs defined by the crate are done so in
70+
/// terms of that specific version.
71+
pub const VIONA_CURRENT_INTERFACE_VERSION: u32 = 2;
72+
6873
pub use ioctls::*;
6974
pub use structs::*;

lib/propolis/src/api_version.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#[derive(Debug, thiserror::Error)]
2+
pub enum Error {
3+
#[error("IO Error")]
4+
Io(#[from] std::io::Error),
5+
6+
#[error("{0} API version {1} did not match expectation {2}")]
7+
Mismatch(&'static str, u32, u32),
8+
}
9+
10+
pub fn check() -> Result<(), Error> {
11+
crate::vmm::check_api_version()?;
12+
crate::hw::virtio::viona::check_api_version()?;
13+
14+
Ok(())
15+
}

lib/propolis/src/hw/virtio/viona.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -692,3 +692,18 @@ pub(crate) mod bits {
692692
pub const VIRTIO_NET_CFG_SIZE: usize = 0xc;
693693
}
694694
use bits::*;
695+
696+
/// Check that available viona API matches expectations of propolis crate
697+
pub(crate) fn check_api_version() -> Result<(), crate::api_version::Error> {
698+
let fd = viona_api::VionaFd::open()?;
699+
let vers = fd.api_version()?;
700+
701+
// viona only requires the V2 bits for now
702+
let compare = viona_api::ApiVersion::V2.into();
703+
704+
if vers < compare {
705+
Err(crate::api_version::Error::Mismatch("viona", vers, compare))
706+
} else {
707+
Ok(())
708+
}
709+
}

lib/propolis/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ pub extern crate usdt;
99
extern crate bitflags;
1010

1111
pub mod accessors;
12+
pub mod api_version;
1213
pub mod block;
1314
pub mod chardev;
1415
pub mod common;

lib/propolis/src/vmm/mod.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,18 @@ pub mod mem;
88
pub use hdl::*;
99
pub use machine::*;
1010
pub use mem::*;
11+
12+
/// Check that available vmm API matches expectations of propolis crate
13+
pub(crate) fn check_api_version() -> Result<(), crate::api_version::Error> {
14+
let ctl = bhyve_api::VmmCtlFd::open()?;
15+
let vers = ctl.api_version()?;
16+
17+
// propolis only requires the bits provided by V8, currently
18+
let compare = bhyve_api::ApiVersion::V8.into();
19+
20+
if vers < compare {
21+
return Err(crate::api_version::Error::Mismatch("vmm", vers, compare));
22+
}
23+
24+
Ok(())
25+
}

0 commit comments

Comments
 (0)