Skip to main content

squib_net/
mode.rs

1//! Host-network mode selectors.
2//!
3//! [`VmnetMode`] maps 1:1 to `vmnet.framework`'s `vmnet_operation_mode_key` enum.
4//! [`NetMode`] is the user-facing CLI choice: vmnet (Shared/Host/Bridged) plus the
5//! Userspace option that bundles `gvproxy` instead.
6
7/// Vmnet operating mode. Mirrors `VMNET_SHARED_MODE` / `VMNET_HOST_MODE` /
8/// `VMNET_BRIDGED_MODE` — the `u64` discriminants come straight from `<vmnet/vmnet.h>`.
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10#[non_exhaustive]
11pub enum VmnetMode {
12    /// `VMNET_SHARED_MODE` — guests get an IP behind a host-side NAT.
13    Shared,
14    /// `VMNET_HOST_MODE` — host-only network (no NAT egress).
15    Host,
16    /// `VMNET_BRIDGED_MODE` — bridged onto a real host interface. Requires the
17    /// restricted `com.apple.vm.networking` entitlement; only compiled in when the
18    /// `bridged` cargo feature is enabled.
19    #[cfg(feature = "bridged")]
20    Bridged,
21}
22
23impl VmnetMode {
24    /// Numeric discriminant used in the XPC dictionary under `vmnet_operation_mode_key`.
25    /// The constants come from `<vmnet/vmnet.h>` (`operating_modes_t`) and are
26    /// stable across macOS versions.
27    #[must_use]
28    pub fn as_xpc_value(self) -> u64 {
29        match self {
30            // VMNET_HOST_MODE = 1000
31            Self::Host => 1000,
32            // VMNET_SHARED_MODE = 1001
33            Self::Shared => 1001,
34            // VMNET_BRIDGED_MODE = 1002
35            #[cfg(feature = "bridged")]
36            Self::Bridged => 1002,
37        }
38    }
39}
40
41/// User-facing network mode. The `Userspace` variant routes through the bundled
42/// `gvproxy` child process; the `Vmnet(_)` variants go through `vmnet.framework`.
43#[derive(Debug, Clone, Copy, PartialEq, Eq)]
44#[non_exhaustive]
45pub enum NetMode {
46    /// vmnet shared/host/bridged.
47    Vmnet(VmnetMode),
48    /// gvproxy bundled-binary userspace TCP/IP.
49    Userspace,
50}
51
52impl NetMode {
53    /// Convenience: shared (NAT) mode.
54    pub const SHARED: Self = Self::Vmnet(VmnetMode::Shared);
55    /// Convenience: host-only mode.
56    pub const HOST: Self = Self::Vmnet(VmnetMode::Host);
57    /// Convenience: gvproxy userspace mode.
58    pub const USERSPACE: Self = Self::Userspace;
59
60    /// Convenience: bridged mode (only available when the `bridged` cargo feature is on).
61    #[cfg(feature = "bridged")]
62    pub const BRIDGED: Self = Self::Vmnet(VmnetMode::Bridged);
63
64    /// Whether this mode requires `com.apple.vm.networking` (the restricted
65    /// entitlement). Only bridged does. Per [30-networking.md §
66    /// 2](../../../specs/30-networking.md#2-modes).
67    #[must_use]
68    pub fn needs_restricted_entitlement(self) -> bool {
69        match self {
70            #[cfg(feature = "bridged")]
71            Self::Vmnet(VmnetMode::Bridged) => true,
72            Self::Vmnet(_) | Self::Userspace => false,
73        }
74    }
75}
76
77#[cfg(test)]
78mod tests {
79    use super::*;
80
81    #[test]
82    fn test_should_map_vmnet_modes_to_xpc_constants() {
83        // Per <vmnet/vmnet.h> operating_modes_t:
84        //   VMNET_HOST_MODE = 1000, VMNET_SHARED_MODE = 1001, VMNET_BRIDGED_MODE = 1002.
85        assert_eq!(VmnetMode::Host.as_xpc_value(), 1000);
86        assert_eq!(VmnetMode::Shared.as_xpc_value(), 1001);
87    }
88
89    #[test]
90    fn test_should_only_flag_bridged_as_restricted_entitlement() {
91        assert!(!NetMode::Vmnet(VmnetMode::Shared).needs_restricted_entitlement());
92        assert!(!NetMode::Vmnet(VmnetMode::Host).needs_restricted_entitlement());
93        assert!(!NetMode::Userspace.needs_restricted_entitlement());
94    }
95
96    #[cfg(feature = "bridged")]
97    #[test]
98    fn test_should_flag_bridged_as_restricted() {
99        assert_eq!(VmnetMode::Bridged.as_xpc_value(), 1002);
100        assert!(NetMode::Vmnet(VmnetMode::Bridged).needs_restricted_entitlement());
101    }
102}