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}