system_configuration/
network_configuration.rs

1//! Bindings for [`SCNetworkConfiguration`].
2//!
3//! [`SCNetworkConfiguration`]: https://developer.apple.com/documentation/systemconfiguration/scnetworkconfiguration?language=objc
4use core_foundation::{
5    array::CFArray,
6    base::{TCFType, ToVoid},
7    string::CFString,
8};
9use system_configuration_sys::network_configuration::{
10    SCNetworkInterfaceCopyAll, SCNetworkInterfaceGetBSDName, SCNetworkInterfaceGetInterfaceType,
11    SCNetworkInterfaceGetLocalizedDisplayName, SCNetworkInterfaceGetTypeID, SCNetworkInterfaceRef,
12    SCNetworkServiceCopyAll, SCNetworkServiceGetEnabled, SCNetworkServiceGetInterface,
13    SCNetworkServiceGetServiceID, SCNetworkServiceGetTypeID, SCNetworkServiceRef,
14    SCNetworkSetCopyCurrent, SCNetworkSetGetServiceOrder, SCNetworkSetGetTypeID, SCNetworkSetRef,
15};
16
17use crate::preferences::SCPreferences;
18
19core_foundation::declare_TCFType!(
20    /// Represents a network interface.
21    ///
22    /// See [`SCNetworkInterfaceRef`] and its [methods] for details.
23    ///
24    /// [`SCNetworkInterfaceRef`]: https://developer.apple.com/documentation/systemconfiguration/scnetworkinterfaceref?language=objc
25    /// [methods]: https://developer.apple.com/documentation/systemconfiguration/scnetworkconfiguration?language=objc
26    SCNetworkInterface,
27    SCNetworkInterfaceRef
28);
29core_foundation::impl_TCFType!(
30    SCNetworkInterface,
31    SCNetworkInterfaceRef,
32    SCNetworkInterfaceGetTypeID
33);
34
35// TODO: implement all the other methods a SCNetworkInterface has
36impl SCNetworkInterface {
37    /// Get type of the network interface, if the type is recognized, returns `None` otherwise.
38    ///
39    /// See [`SCNetworkInterfaceGetInterfaceType`] for details.
40    ///
41    /// [`SCNetworkInterfaceGetInterfaceType`]: https://developer.apple.com/documentation/systemconfiguration/1517371-scnetworkinterfacegetinterfacety?language=objc
42    pub fn interface_type(&self) -> Option<SCNetworkInterfaceType> {
43        SCNetworkInterfaceType::from_cfstring(&self.interface_type_string()?)
44    }
45
46    /// Returns the raw interface type identifier.
47    ///
48    /// See [`SCNetworkInterfaceGetInterfaceType`] for details.
49    ///
50    /// [`SCNetworkInterfaceGetInterfaceType`]: https://developer.apple.com/documentation/systemconfiguration/1517371-scnetworkinterfacegetinterfacety?language=objc
51    pub fn interface_type_string(&self) -> Option<CFString> {
52        unsafe {
53            let ptr = SCNetworkInterfaceGetInterfaceType(self.0);
54            if ptr.is_null() {
55                None
56            } else {
57                Some(CFString::wrap_under_get_rule(ptr))
58            }
59        }
60    }
61
62    /// Returns the _BSD_ name for the interface, such as `en0`.
63    ///
64    /// See [`SCNetworkInterfaceGetBSDName`] for details.
65    ///
66    /// [`SCNetworkInterfaceGetBSDName`]: https://developer.apple.com/documentation/systemconfiguration/1516854-scnetworkinterfacegetbsdname?language=objc
67    pub fn bsd_name(&self) -> Option<CFString> {
68        unsafe {
69            let ptr = SCNetworkInterfaceGetBSDName(self.0);
70            if ptr.is_null() {
71                None
72            } else {
73                Some(CFString::wrap_under_get_rule(ptr))
74            }
75        }
76    }
77
78    /// Returns the localized display name for the interface.
79    ///
80    /// See [`SCNetworkInterfaceGetLocalizedDisplayName`] for details.
81    ///
82    /// [`SCNetworkInterfaceGetLocalizedDisplayName`]: https://developer.apple.com/documentation/systemconfiguration/1517060-scnetworkinterfacegetlocalizeddi?language=objc
83    pub fn display_name(&self) -> Option<CFString> {
84        unsafe {
85            let ptr = SCNetworkInterfaceGetLocalizedDisplayName(self.0);
86            if ptr.is_null() {
87                None
88            } else {
89                Some(CFString::wrap_under_get_rule(ptr))
90            }
91        }
92    }
93}
94
95/// Represents the possible network interface types.
96///
97/// See [_Network Interface Types_] documentation for details.
98///
99/// [_Network Interface Types_]: https://developer.apple.com/documentation/systemconfiguration/scnetworkconfiguration/network_interface_types?language=objc
100#[derive(Debug)]
101pub enum SCNetworkInterfaceType {
102    /// A 6to4 interface.
103    SixToFour,
104    /// Bluetooth interface.
105    Bluetooth,
106    /// Bridge interface.
107    Bridge,
108    /// Ethernet bond interface.
109    Bond,
110    /// Ethernet interface.
111    Ethernet,
112    /// FireWire interface.
113    FireWire,
114    /// IEEE80211 interface.
115    IEEE80211,
116    /// IPSec interface.
117    IPSec,
118    /// IrDA interface.
119    IrDA,
120    /// L2TP interface.
121    L2TP,
122    /// Modem interface.
123    Modem,
124    /// PPP interface.
125    PPP,
126    /// PPTP interface.
127    ///
128    /// Deprecated, one should use the PPP variant.
129    PPTP,
130    /// Serial interface.
131    Serial,
132    /// VLAN interface.
133    VLAN,
134    /// WWAN interface.
135    WWAN,
136    /// IPv4 interface.
137    IPv4,
138}
139
140/// Bridge interface type referred to as `kSCNetworkInterfaceTypeBridge` in private headers.
141static BRIDGE_INTERFACE_TYPE_ID: &str = "Bridge";
142
143/// IrDA interface referenced as `kSCNetworkInterfaceTypeIrDA` but deprecated since macOS 12.
144static IRDA_INTERFACE_TYPE_ID: &str = "IrDA";
145
146impl SCNetworkInterfaceType {
147    /// Tries to construct a type by matching it to string constants used to identify a network
148    /// interface type. If no constants match it, `None` is returned.
149    pub fn from_cfstring(type_id: &CFString) -> Option<Self> {
150        use system_configuration_sys::network_configuration::*;
151
152        let id_is_equal_to = |const_str| -> bool {
153            let const_str = unsafe { CFString::wrap_under_get_rule(const_str) };
154            &const_str == type_id
155        };
156        unsafe {
157            if id_is_equal_to(kSCNetworkInterfaceType6to4) {
158                Some(SCNetworkInterfaceType::SixToFour)
159            } else if id_is_equal_to(kSCNetworkInterfaceTypeBluetooth) {
160                Some(SCNetworkInterfaceType::Bluetooth)
161            } else if type_id == &BRIDGE_INTERFACE_TYPE_ID {
162                Some(SCNetworkInterfaceType::Bridge)
163            } else if id_is_equal_to(kSCNetworkInterfaceTypeBond) {
164                Some(SCNetworkInterfaceType::Bond)
165            } else if id_is_equal_to(kSCNetworkInterfaceTypeEthernet) {
166                Some(SCNetworkInterfaceType::Ethernet)
167            } else if id_is_equal_to(kSCNetworkInterfaceTypeFireWire) {
168                Some(SCNetworkInterfaceType::FireWire)
169            } else if id_is_equal_to(kSCNetworkInterfaceTypeIEEE80211) {
170                Some(SCNetworkInterfaceType::IEEE80211)
171            } else if id_is_equal_to(kSCNetworkInterfaceTypeIPSec) {
172                Some(SCNetworkInterfaceType::IPSec)
173            } else if type_id == &IRDA_INTERFACE_TYPE_ID {
174                Some(SCNetworkInterfaceType::IrDA)
175            } else if id_is_equal_to(kSCNetworkInterfaceTypeL2TP) {
176                Some(SCNetworkInterfaceType::L2TP)
177            } else if id_is_equal_to(kSCNetworkInterfaceTypeModem) {
178                Some(SCNetworkInterfaceType::Modem)
179            } else if id_is_equal_to(kSCNetworkInterfaceTypePPP) {
180                Some(SCNetworkInterfaceType::PPP)
181            } else if id_is_equal_to(kSCNetworkInterfaceTypePPTP) {
182                Some(SCNetworkInterfaceType::PPTP)
183            } else if id_is_equal_to(kSCNetworkInterfaceTypeSerial) {
184                Some(SCNetworkInterfaceType::Serial)
185            } else if id_is_equal_to(kSCNetworkInterfaceTypeVLAN) {
186                Some(SCNetworkInterfaceType::VLAN)
187            } else if id_is_equal_to(kSCNetworkInterfaceTypeWWAN) {
188                Some(SCNetworkInterfaceType::WWAN)
189            } else if id_is_equal_to(kSCNetworkInterfaceTypeIPv4) {
190                Some(SCNetworkInterfaceType::IPv4)
191            } else {
192                None
193            }
194        }
195    }
196}
197
198/// Retrieve all current network interfaces
199///
200/// See [`SCNetworkInterfaceCopyAll`] for more details.
201///
202/// [`SCNetworkInterfaceCopyAll`]: https://developer.apple.com/documentation/systemconfiguration/1517090-scnetworkinterfacecopyall?language=objc
203pub fn get_interfaces() -> CFArray<SCNetworkInterface> {
204    unsafe { CFArray::<SCNetworkInterface>::wrap_under_create_rule(SCNetworkInterfaceCopyAll()) }
205}
206
207core_foundation::declare_TCFType!(
208    /// Represents a network service.
209    ///
210    /// See [`SCNetworkInterfaceRef`] and its [methods] for details.
211    ///
212    /// [`SCNetworkInterfaceRef`]: https://developer.apple.com/documentation/systemconfiguration/scnetworkserviceref?language=objc
213    /// [methods]: https://developer.apple.com/documentation/systemconfiguration/scnetworkconfiguration?language=objc
214    SCNetworkService,
215    SCNetworkServiceRef
216);
217
218core_foundation::impl_TCFType!(
219    SCNetworkService,
220    SCNetworkServiceRef,
221    SCNetworkServiceGetTypeID
222);
223
224impl SCNetworkService {
225    /// Returns an array of all network services
226    pub fn get_services(prefs: &SCPreferences) -> CFArray<Self> {
227        unsafe {
228            let array_ptr = SCNetworkServiceCopyAll(prefs.to_void());
229            if array_ptr.is_null() {
230                return create_empty_array();
231            }
232            CFArray::<Self>::wrap_under_create_rule(array_ptr)
233        }
234    }
235
236    /// Returns true if the network service is currently enabled
237    pub fn enabled(&self) -> bool {
238        unsafe { SCNetworkServiceGetEnabled(self.0) == 0 }
239    }
240
241    /// Returns the network interface backing this network service, if it has one.
242    pub fn network_interface(&self) -> Option<SCNetworkInterface> {
243        unsafe {
244            let ptr = SCNetworkServiceGetInterface(self.0);
245            if ptr.is_null() {
246                None
247            } else {
248                Some(SCNetworkInterface::wrap_under_get_rule(ptr))
249            }
250        }
251    }
252
253    /// Returns the service identifier.
254    pub fn id(&self) -> Option<CFString> {
255        unsafe {
256            let ptr = SCNetworkServiceGetServiceID(self.0);
257            if ptr.is_null() {
258                None
259            } else {
260                Some(CFString::wrap_under_get_rule(ptr))
261            }
262        }
263    }
264}
265
266core_foundation::declare_TCFType!(
267    /// Represents a complete network configuration for a particular host.
268    ///
269    /// See [`SCNetworkSet`] for details.
270    ///
271    /// [`SCNetworkSet`]: https://developer.apple.com/documentation/systemconfiguration/scnetworksetref?language=objc
272    SCNetworkSet,
273    SCNetworkSetRef
274);
275
276core_foundation::impl_TCFType!(SCNetworkSet, SCNetworkSetRef, SCNetworkSetGetTypeID);
277
278impl SCNetworkSet {
279    /// Constructs a new set of network services from the preferences.
280    pub fn new(prefs: &SCPreferences) -> Self {
281        let ptr = unsafe { SCNetworkSetCopyCurrent(prefs.to_void()) };
282        unsafe { SCNetworkSet::wrap_under_create_rule(ptr) }
283    }
284
285    /// Returns an list of network service identifiers, ordered by their priority.
286    pub fn service_order(&self) -> CFArray<CFString> {
287        unsafe {
288            let array_ptr = SCNetworkSetGetServiceOrder(self.0);
289            if array_ptr.is_null() {
290                return create_empty_array();
291            }
292            CFArray::<CFString>::wrap_under_get_rule(array_ptr)
293        }
294    }
295}
296
297fn create_empty_array<T>() -> CFArray<T> {
298    use std::ptr::null;
299    unsafe {
300        CFArray::wrap_under_create_rule(core_foundation::array::CFArrayCreate(
301            null() as *const _,
302            null() as *const _,
303            0,
304            null() as *const _,
305        ))
306    }
307}
308
309#[cfg(test)]
310mod test {
311    use super::*;
312
313    #[test]
314    fn test_get_all_interfaces() {
315        let _ = get_interfaces();
316    }
317
318    #[test]
319    fn test_get_type() {
320        for iface in get_interfaces().into_iter() {
321            if iface.interface_type().is_none() {
322                panic!(
323                    "Interface  {:?} ({:?}) has unrecognized type {:?}",
324                    iface.display_name(),
325                    iface.bsd_name(),
326                    iface.interface_type_string()
327                )
328            }
329        }
330    }
331
332    #[test]
333    fn test_service_order() {
334        let prefs = SCPreferences::default(&CFString::new("test"));
335        let services = SCNetworkService::get_services(&prefs);
336        let set = SCNetworkSet::new(&prefs);
337        let service_order = set.service_order();
338
339        assert!(service_order.iter().all(|service_id| {
340            services
341                .iter()
342                .any(|service| service.id().as_ref() == Some(&*service_id))
343        }))
344    }
345
346    #[test]
347    fn test_empty_array() {
348        let empty = create_empty_array::<CFString>();
349        let values = empty.get_all_values();
350        assert!(values.is_empty())
351    }
352}