use std::str::FromStr;
use crate::edge::EdgeError;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EdgeMode {
Auto,
Netstack,
Device,
}
impl FromStr for EdgeMode {
type Err = EdgeError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.trim().to_ascii_lowercase().as_str() {
"auto" => Ok(Self::Auto),
"netstack" => Ok(Self::Netstack),
"device" => Ok(Self::Device),
other => Err(EdgeError::Probe(format!(
"unknown edge mode {other:?} (expected one of: auto, netstack, device)"
))),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DeviceVerdict {
Grantable,
Denied(String),
}
#[must_use]
pub fn probe_device_mode() -> DeviceVerdict {
#[cfg(target_os = "linux")]
{
let has_cap = crate::capability::probe_has_cap_net_admin();
let has_dev = crate::capability::probe_tun_device_available();
match (has_cap, has_dev) {
(true, true) => DeviceVerdict::Grantable,
(false, true) => DeviceVerdict::Denied(
"no CAP_NET_ADMIN in the effective capability set".to_string(),
),
(true, false) => DeviceVerdict::Denied("/dev/net/tun is not openable".to_string()),
(false, false) => DeviceVerdict::Denied(
"no CAP_NET_ADMIN and /dev/net/tun is not openable".to_string(),
),
}
}
#[cfg(target_os = "macos")]
{
if zlayer_paths::is_root() {
DeviceVerdict::Grantable
} else {
DeviceVerdict::Denied("utun requires root on macOS".to_string())
}
}
#[cfg(target_os = "windows")]
{
if zlayer_paths::is_root() {
DeviceVerdict::Grantable
} else {
DeviceVerdict::Denied("Wintun requires Administrator on Windows".to_string())
}
}
#[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))]
{
DeviceVerdict::Denied("TUN device mode is unsupported on this platform".to_string())
}
}
#[cfg(test)]
mod tests {
use super::{DeviceVerdict, EdgeMode};
use std::str::FromStr;
#[test]
fn edge_mode_from_str_accepts_all_three() {
assert_eq!(EdgeMode::from_str("auto").unwrap(), EdgeMode::Auto);
assert_eq!(EdgeMode::from_str("netstack").unwrap(), EdgeMode::Netstack);
assert_eq!(EdgeMode::from_str("device").unwrap(), EdgeMode::Device);
}
#[test]
fn edge_mode_from_str_is_case_insensitive_and_trims() {
assert_eq!(EdgeMode::from_str("AUTO").unwrap(), EdgeMode::Auto);
assert_eq!(EdgeMode::from_str("NetStack").unwrap(), EdgeMode::Netstack);
assert_eq!(EdgeMode::from_str(" Device ").unwrap(), EdgeMode::Device);
}
#[test]
fn edge_mode_from_str_rejects_unknown() {
let err = EdgeMode::from_str("bridge").unwrap_err();
let msg = err.to_string();
assert!(
msg.contains("bridge"),
"reason should echo the bad value: {msg}"
);
assert!(
msg.contains("edge probe error"),
"should be a Probe error: {msg}"
);
}
#[test]
fn probe_device_mode_is_total_and_coherent() {
match super::probe_device_mode() {
DeviceVerdict::Grantable => {}
DeviceVerdict::Denied(reason) => {
assert!(!reason.is_empty(), "Denied must carry a human reason");
}
}
}
}