#![forbid(unsafe_code)]
use std::time::Duration;
use thiserror::Error;
#[cfg(target_os = "macos")]
use tracing::debug;
use tracing::warn;
use crate::mode::VmnetMode;
#[cfg(target_os = "macos")]
pub use crate::sys::iface_impl::{BATCH, IfaceStats};
#[cfg(not(target_os = "macos"))]
pub const BATCH: usize = 32;
#[cfg(not(target_os = "macos"))]
#[derive(Debug, Default, Clone, Copy)]
pub struct IfaceStats {
pub rx_bytes: u64,
pub tx_bytes: u64,
pub rx_frames: u64,
pub tx_frames: u64,
}
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum IfaceError {
#[error("vmnet not supported on this platform")]
NotSupported,
#[cfg(target_os = "macos")]
#[error(transparent)]
Inner(#[from] crate::sys::iface_impl::InnerError),
}
pub const DEFAULT_START_TIMEOUT: Duration = Duration::from_secs(5);
#[derive(Debug, Clone)]
pub struct InterfaceParams {
pub iface_id: String,
pub mode: VmnetMode,
pub bridged_iface_name: Option<String>,
pub mtu: Option<u32>,
pub start_timeout: Duration,
pub enable_isolation: bool,
}
impl InterfaceParams {
#[must_use]
pub fn new(iface_id: impl Into<String>, mode: VmnetMode) -> Self {
Self {
iface_id: iface_id.into(),
mode,
bridged_iface_name: None,
mtu: None,
start_timeout: DEFAULT_START_TIMEOUT,
enable_isolation: true,
}
}
}
#[derive(Debug)]
pub struct VmnetIface {
iface_id: String,
#[cfg(target_os = "macos")]
inner: Option<crate::sys::iface_impl::Inner>,
#[cfg(not(target_os = "macos"))]
_phantom: std::marker::PhantomData<()>,
}
impl VmnetIface {
#[cfg_attr(not(target_os = "macos"), allow(clippy::needless_pass_by_value))]
pub fn start(params: InterfaceParams) -> Result<Self, IfaceError> {
#[cfg(target_os = "macos")]
{
let timeout_ms = u64::try_from(params.start_timeout.as_millis()).unwrap_or(u64::MAX);
let inner_params = crate::sys::iface_impl::StartParams {
iface_id: params.iface_id.clone(),
mode: params.mode.as_xpc_value(),
bridged_iface_name: params.bridged_iface_name,
mtu: params.mtu,
start_timeout_ms: timeout_ms,
enable_isolation: params.enable_isolation,
};
let inner = crate::sys::iface_impl::Inner::start(&inner_params)?;
debug!(
iface_id = %params.iface_id,
mtu = inner.mtu(),
"vmnet iface started"
);
Ok(Self {
iface_id: params.iface_id,
inner: Some(inner),
})
}
#[cfg(not(target_os = "macos"))]
{
let _ = params;
Err(IfaceError::NotSupported)
}
}
#[must_use]
pub fn iface_id(&self) -> &str {
&self.iface_id
}
#[must_use]
pub fn mtu(&self) -> u32 {
#[cfg(target_os = "macos")]
{
self.inner
.as_ref()
.map_or(1500, crate::sys::iface_impl::Inner::mtu)
}
#[cfg(not(target_os = "macos"))]
{
1500
}
}
#[must_use]
pub fn host_mac(&self) -> [u8; 6] {
#[cfg(target_os = "macos")]
{
self.inner
.as_ref()
.map_or([0; 6], crate::sys::iface_impl::Inner::host_mac)
}
#[cfg(not(target_os = "macos"))]
{
[0; 6]
}
}
pub fn read(&self, frames: &mut [&mut [u8]], sizes: &mut [usize]) -> Result<usize, IfaceError> {
#[cfg(target_os = "macos")]
{
self.inner.as_ref().map_or_else(
|| Err(IfaceError::NotSupported),
|inner| Ok(inner.read_into_sized(frames, sizes)?),
)
}
#[cfg(not(target_os = "macos"))]
{
let _ = (frames, sizes);
Err(IfaceError::NotSupported)
}
}
pub fn write(&self, frames: &[&[u8]]) -> Result<usize, IfaceError> {
#[cfg(target_os = "macos")]
{
self.inner.as_ref().map_or_else(
|| Err(IfaceError::NotSupported),
|inner| Ok(inner.write_frames(frames)?),
)
}
#[cfg(not(target_os = "macos"))]
{
let _ = frames;
Err(IfaceError::NotSupported)
}
}
#[must_use]
pub fn stats(&self) -> IfaceStats {
#[cfg(target_os = "macos")]
{
self.inner.as_ref().map_or_else(
IfaceStats::default,
crate::sys::iface_impl::Inner::stats_snapshot,
)
}
#[cfg(not(target_os = "macos"))]
{
IfaceStats::default()
}
}
pub fn stop(&mut self) -> Result<(), IfaceError> {
#[cfg(target_os = "macos")]
{
if let Some(mut inner) = self.inner.take() {
inner.stop()?;
}
Ok(())
}
#[cfg(not(target_os = "macos"))]
{
Ok(())
}
}
}
impl Drop for VmnetIface {
fn drop(&mut self) {
if let Err(e) = self.stop() {
warn!(iface_id = %self.iface_id, error = %e, "vmnet stop on Drop failed");
}
}
}