From a75deee328f9414394aa55d249037d549eb86532 Mon Sep 17 00:00:00 2001 From: luozijun Date: Wed, 21 Feb 2018 20:07:25 +0800 Subject: [PATCH] Add High-level API --- system-configuration/Cargo.toml | 4 +- .../examples/network_configuration.rs | 38 ++ system-configuration/examples/set_dns.rs | 95 ++-- system-configuration/src/lib.rs | 6 +- .../src/network_configuration.rs | 441 ++++++++++++++++++ system-configuration/src/preferences.rs | 49 ++ 6 files changed, 590 insertions(+), 43 deletions(-) create mode 100644 system-configuration/examples/network_configuration.rs create mode 100644 system-configuration/src/network_configuration.rs create mode 100644 system-configuration/src/preferences.rs diff --git a/system-configuration/Cargo.toml b/system-configuration/Cargo.toml index ffe77b3..fc65957 100644 --- a/system-configuration/Cargo.toml +++ b/system-configuration/Cargo.toml @@ -11,5 +11,7 @@ readme = "../README.md" [dependencies] core-foundation = "0.5" - system-configuration-sys = { path = "../system-configuration-sys", version = "0.1" } + +[features] +nightly = [] diff --git a/system-configuration/examples/network_configuration.rs b/system-configuration/examples/network_configuration.rs new file mode 100644 index 0000000..e61dc32 --- /dev/null +++ b/system-configuration/examples/network_configuration.rs @@ -0,0 +1,38 @@ +extern crate core_foundation; +extern crate system_configuration; + +use core_foundation::base::kCFAllocatorDefault; + +use system_configuration::dynamic_store::SCDynamicStoreBuilder; +use system_configuration::network_configuration::{global_router, SCNetworkInterface, + SCNetworkService}; +use system_configuration::preferences::SCPreferences; + +// This example will output network-global-service, network-global-interface, network-global-router, +// network-service-order-list, network-services and network-interfaces to stdout. + +fn main() { + let session_name = "session_name"; + let prefs = SCPreferences::new(unsafe { kCFAllocatorDefault }, session_name, None); + let store = SCDynamicStoreBuilder::new(session_name).build(); + + let service = SCNetworkService::global(&prefs, &store).unwrap(); + println!("Global Service:\n{:?}\n", service); + println!("Global Interface:\n{:?}\n", service.interface()); + println!("Global Service Router:\n{:?}\n", global_router(&store)); + + println!("\n-listnetworkserviceorder:"); + for service in SCNetworkService::list_order(&prefs) { + println!("{:?}", service); + } + + println!("\n-listallnetworkservices:"); + for service in SCNetworkService::list(&prefs) { + println!("{:?}", service); + } + + println!("\n-listallnetworkinterface:"); + for interface in SCNetworkInterface::list() { + println!("{:?}", interface); + } +} diff --git a/system-configuration/examples/set_dns.rs b/system-configuration/examples/set_dns.rs index 07836a2..263f471 100644 --- a/system-configuration/examples/set_dns.rs +++ b/system-configuration/examples/set_dns.rs @@ -1,53 +1,66 @@ +extern crate core_foundation; extern crate system_configuration; -extern crate core_foundation; +use core_foundation::base::kCFAllocatorDefault; + + +use system_configuration::dynamic_store::SCDynamicStoreBuilder; +use system_configuration::network_configuration::SCNetworkService; +use system_configuration::preferences::SCPreferences; -use core_foundation::array::CFArray; -use core_foundation::base::TCFType; -use core_foundation::dictionary::CFDictionary; -use core_foundation::propertylist::CFPropertyList; -use core_foundation::string::{CFString, CFStringRef}; -use system_configuration::dynamic_store::{SCDynamicStore, SCDynamicStoreBuilder}; +use std::net::{IpAddr, Ipv4Addr}; + // This example will change the DNS settings on the primary // network interface to 8.8.8.8 and 8.8.4.4 +// Usage: + +// $ cargo build --example set_dns +// $ sudo ../target/debug/examples/set_dns + fn main() { - let store = SCDynamicStoreBuilder::new("my-test-dyn-store").build(); - let primary_service_uuid = get_primary_service_uuid(&store).expect("No PrimaryService active"); - println!("PrimaryService UUID: {}", primary_service_uuid); - - let primary_service_path = CFString::new(&format!( - "State:/Network/Service/{}/DNS", - primary_service_uuid - )); - println!("PrimaryService path: {}", primary_service_path); - - let dns_dictionary = create_dns_dictionary(&[ - CFString::from_static_string("8.8.8.8"), - CFString::from_static_string("8.8.4.4"), - ]); - - let success = store.set(primary_service_path, dns_dictionary); - println!("success? {}", success); -} + let addrs = vec![ + IpAddr::V4(Ipv4Addr::new(8, 8, 8, 8)), + IpAddr::V4(Ipv4Addr::new(8, 8, 4, 4)), + ]; -fn get_primary_service_uuid(store: &SCDynamicStore) -> Option { - let dictionary = store - .get("State:/Network/Global/IPv4") - .and_then(CFPropertyList::downcast_into::); - if let Some(dictionary) = dictionary { - dictionary - .find2(&CFString::from_static_string("PrimaryService")) - .map(|ptr| unsafe { CFString::wrap_under_get_rule(ptr as CFStringRef) }) - } else { - None - } -} + let session_name = "session_name"; + let store = SCDynamicStoreBuilder::new(session_name).build(); + let prefs = SCPreferences::new(unsafe { kCFAllocatorDefault }, session_name, None); + + let global_service = + SCNetworkService::global(&prefs, &store).expect("No PrimaryService active"); + let global_interface = global_service + .interface() + .expect("No PrimaryInterface active"); + + println!("Global Service:"); + println!("\tid: {:?}", global_service.id()); + println!("\tname: {:?}", global_service.name()); + println!("\tenabled: {:?}", global_service.enabled()); + println!("\tdns: {:?}", global_service.dns(&store)); + println!("\tinterface: {:?}", global_interface.name().unwrap()); + + println!( + "Set dns to {:?} on {:?} service ...", + addrs, + global_service.name() + ); + + + println!( + "Success: {:?}", + global_service.set_dns_server_addresses(&store, Some(addrs)) + ); + + // Check + // `networksetup -getdnsservers "Wi-Fi"` Or `scutil --dns` Or `dig` + println!("{:?}", global_service.dns(&store)); -fn create_dns_dictionary(addresses: &[CFString]) -> CFDictionary { - let key = CFString::from_static_string("ServerAddresses"); - let value = CFArray::from_CFTypes(addresses); - CFDictionary::from_CFType_pairs(&[(key, value)]) + println!( + "\n\nUse Command `networksetup -setdnsservers \"{}\" \"Empty\"` to restore DNS setting. ", + global_service.name() + ); } diff --git a/system-configuration/src/lib.rs b/system-configuration/src/lib.rs index cf52342..c4a3a59 100644 --- a/system-configuration/src/lib.rs +++ b/system-configuration/src/lib.rs @@ -16,11 +16,15 @@ //! //! [SystemConfiguration]: https://developer.apple.com/documentation/systemconfiguration?language=objc //! [`system-configuration-sys`]: https://crates.io/crates/system-configuration-sys - +#![cfg_attr(all(feature = "nightly", test), feature(test))] #![deny(missing_docs)] #[macro_use] extern crate core_foundation; extern crate system_configuration_sys; +#[cfg(all(feature = "nightly", test))] +extern crate test; pub mod dynamic_store; +pub mod preferences; +pub mod network_configuration; diff --git a/system-configuration/src/network_configuration.rs b/system-configuration/src/network_configuration.rs new file mode 100644 index 0000000..2d54bfe --- /dev/null +++ b/system-configuration/src/network_configuration.rs @@ -0,0 +1,441 @@ +// Copyright 2017 Amagicom AB. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Bindings to [`SCNetworkConfiguration`]. +//! +//! See the examples directory for examples how to use this module. +//! +//! [`SCNetworkConfiguration`]: https://developer.apple.com/documentation/systemconfiguration/scnetworkconfiguration + + +use core_foundation::array::CFArray; +use core_foundation::base::{CFType, TCFType}; +use core_foundation::dictionary::CFDictionary; +use core_foundation::string::CFString; + +use dynamic_store::SCDynamicStore; +use preferences::SCPreferences; + +pub use system_configuration_sys::network_configuration::*; + +use std::fmt; +use std::net::IpAddr; + +/// MTU +#[derive(Debug)] +pub struct SCNetworkInterfaceMtu { + /// the current MTU setting for the interface. + pub current: u32, + /// the minimum MTU setting for the interface. If None, the minimum setting could not + /// be determined. + pub min: Option, + /// the maximum MTU setting for the interface. If None, the maximum setting could not + /// be determined. + pub max: Option, +} + +/// DNS +#[derive(Debug)] +pub struct SCNetworkServiceDns { + /// State DNS setting + pub state: DnsSetting, + /// Setup DNS setting + pub setup: DnsSetting, +} + +/// DNS Setting +#[derive(Debug)] +pub struct DnsSetting { + /// Domain Name + pub domain_name: Option, + /// DNS Server Addresses + pub server_addresses: Option>, +} + + +fn global_query(store: &SCDynamicStore, key: &str) -> Option { + let path = CFString::from_static_string("State:/Network/Global/IPv4"); + if let Some(value) = store.get(path.clone()) { + if let Some(dict) = value.downcast_into::() { + if let Some(val) = dict.find2(&CFString::new(key)) { + let value = unsafe { CFType::wrap_under_get_rule(val) }; + if let Some(value) = value.downcast::() { + return Some(value.to_string()); + } + } + } + } + + return None; +} + + +/// Returns default route on primary network service. +pub fn global_router(store: &SCDynamicStore) -> Option { + if let Some(router_str) = global_query(store, "Router") { + if let Ok(router_ip) = router_str.parse::() { + return Some(router_ip); + } + } + + return None; +} + + +declare_TCFType!{ + /// Network service object. + SCNetworkService, SCNetworkServiceRef +} + +impl_TCFType!( + SCNetworkService, + SCNetworkServiceRef, + SCNetworkServiceGetTypeID +); + + +impl SCNetworkService { + /// Returns primary network service + pub fn global(prefs: &SCPreferences, store: &SCDynamicStore) -> Option { + match global_query(store, "PrimaryService") { + Some(service_id) => SCNetworkService::from_id(prefs, &service_id), + None => None, + } + } + + /// Returns network service for the specified preferences session and service ID. + pub fn from_id(prefs: &SCPreferences, service_id: &str) -> Option { + let network_service_ref = unsafe { + SCNetworkServiceCopy( + prefs.as_concrete_TypeRef(), + CFString::new(service_id).as_concrete_TypeRef(), + ) + }; + + if network_service_ref.is_null() { + None + } else { + Some(unsafe { SCNetworkService::wrap_under_get_rule(network_service_ref) }) + } + } + + /// Returns all available network services for the specified preferences. + pub fn list(prefs: &SCPreferences) -> Vec { + let array: CFArray = unsafe { + CFArray::wrap_under_get_rule(SCNetworkServiceCopyAll(prefs.as_concrete_TypeRef())) + }; + + array + .iter() + .map(|item| item.downcast::().unwrap()) + .collect::>() + + + // let array: CFArray = unsafe { + // CFArray::wrap_under_get_rule(SCNetworkServiceCopyAll(prefs.as_concrete_TypeRef())) + // }; + + // array + // .iter() + // .map(|item| item.as_CFType().downcast::().unwrap()) + // .collect::>() + } + + /// Returns the user-specified ordering of network services within the specified + /// preferences. + pub fn list_order(prefs: &SCPreferences) -> Vec { + let mut services = Vec::new(); + + let netset = unsafe { SCNetworkSetCopyCurrent(prefs.as_concrete_TypeRef()) }; + + let array: CFArray = unsafe { + let arr_ref = SCNetworkSetGetServiceOrder(netset); + if arr_ref.is_null() { + return services; + } + CFArray::wrap_under_get_rule(arr_ref) + }; + + for item in array.iter() { + if let Some(id) = item.downcast::() { + if let Some(serv) = SCNetworkService::from_id(prefs, id.to_string().as_str()) { + services.push(serv); + } + } + } + + services + } + + /// Returns the identifier for this network service. + pub fn id(&self) -> String { + unsafe { CFString::wrap_under_get_rule(SCNetworkServiceGetServiceID(self.0)) }.to_string() + } + + /// Returns the user-specified name associated with this network service. + pub fn name(&self) -> String { + unsafe { CFString::wrap_under_get_rule(SCNetworkServiceGetName(self.0)) }.to_string() + } + + /// Returns this network service is enabled or disabled. + pub fn enabled(&self) -> bool { + let ret = unsafe { SCNetworkServiceGetEnabled(self.0) }; + ret == 1 + } + + /// Returns the DNS infomation on this network service + pub fn dns(&self, store: &SCDynamicStore) -> SCNetworkServiceDns { + let query = |path: String| -> DnsSetting { + let mut dns_domain_name: Option = None; + let mut dns_server_addresses: Option> = None; + + if let Some(value) = store.get(CFString::new(&path)) { + if let Some(dict) = value.downcast_into::() { + if let Some(domain_name) = + dict.find2(&CFString::from_static_string("DomainName")) + { + let domain_name = unsafe { CFType::wrap_under_get_rule(domain_name) }; + if let Some(domain_name) = domain_name.downcast::() { + dns_domain_name = Some(domain_name.to_string()); + } + } + + if let Some(addrs) = + dict.find2(&CFString::from_static_string("ServerAddresses")) + { + let addrs = unsafe { CFType::wrap_under_get_rule(addrs) }; + if let Some(addrs) = addrs.downcast::>() { + let mut temp = Vec::new(); + for addr in addrs.iter() { + if let Some(addr) = addr.downcast::() { + if let Ok(ip_addr) = addr.to_string().parse::() { + temp.push(ip_addr); + } + } + } + + if temp.len() > 0 { + dns_server_addresses = Some(temp); + } + } + } + } + } + + DnsSetting { + domain_name: dns_domain_name, + server_addresses: dns_server_addresses, + } + }; + + let state_dns_setting = query(format!("State:/Network/Service/{}/DNS", self.id())); + let setup_dns_setting = query(format!("Setup:/Network/Service/{}/DNS", self.id())); + + SCNetworkServiceDns { + state: state_dns_setting, + setup: setup_dns_setting, + } + } + + /// Setting DNS Domain Name on this network service + pub fn set_dns_domain_name(&self, store: &SCDynamicStore, domain_name: Option) -> bool { + let key = CFString::from_static_string("DomainName"); + let value = CFString::new(domain_name.unwrap_or("Empty".to_string()).as_str()); + let dictionary = CFDictionary::from_CFType_pairs(&[(key, value)]); + + let path = CFString::new(&format!("Setup:/Network/Service/{}/DNS", self.id())); + + store.set(path, dictionary) + } + + /// Setting DNS Server Addresses on this network service + pub fn set_dns_server_addresses( + &self, + store: &SCDynamicStore, + server_addrs: Option>, + ) -> bool { + let key = CFString::from_static_string("ServerAddresses"); + let addrs: Vec = match server_addrs { + Some(addrs) => addrs + .iter() + .map(|s| CFString::new(&format!("{}", s))) + .collect(), + None => vec![CFString::new("Empty")], + }; + let value = CFArray::from_CFTypes(&addrs); + let dictionary = CFDictionary::from_CFType_pairs(&[(key, value)]); + + let path = CFString::new(&format!("Setup:/Network/Service/{}/DNS", self.id())); + + store.set(path, dictionary) + } + + /// Returns the network interface associated with this network service. + pub fn interface(&self) -> Option { + let interface_ref = + unsafe { CFType::wrap_under_get_rule(SCNetworkServiceGetInterface(self.0)) }; + interface_ref.downcast::() + } +} + +impl fmt::Display for SCNetworkService { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:?}", self) + } +} + +impl fmt::Debug for SCNetworkService { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "SCNetworkService{{ id: {:?}, name: {:?}, enabled: {}, interface: {:?} }}", + self.id(), + self.name(), + self.enabled(), + self.interface(), + ) + } +} + + +declare_TCFType!{ + /// Network interface object. + SCNetworkInterface, SCNetworkInterfaceRef +} + +impl_TCFType!( + SCNetworkInterface, + SCNetworkInterfaceRef, + SCNetworkInterfaceGetTypeID +); + + +impl SCNetworkInterface { + /// Returns all network-capable interfaces on the system. + pub fn list() -> Vec { + let array: CFArray = + unsafe { CFArray::wrap_under_get_rule(SCNetworkInterfaceCopyAll()) }; + + array + .iter() + .map(|item| item.downcast::().unwrap()) + .collect::>() + } + + /// Returns the current MTU setting and the range of allowable values + pub fn mtu(&self) -> Option { + let mut current = 0i32; + let mut min = 0i32; + let mut max = 0i32; + + let ret_code = + unsafe { SCNetworkInterfaceCopyMTU(self.0, &mut current, &mut min, &mut max) }; + if ret_code == 0 { + None + } else { + Some(SCNetworkInterfaceMtu { + current: current as u32, + min: if min < 0 { None } else { Some(min as u32) }, + max: if max < 0 { None } else { Some(max as u32) }, + }) + } + } + + /// Returns the BSD interface or device name + pub fn name(&self) -> Option { + unsafe { + let str_ptr = SCNetworkInterfaceGetBSDName(self.0); + if str_ptr.is_null() { + None + } else { + Some(CFString::wrap_under_get_rule(str_ptr).to_string()) + } + } + } + + /// Returns the network interface type + pub fn interface_type(&self) -> String { + unsafe { + CFString::wrap_under_get_rule(SCNetworkInterfaceGetInterfaceType(self.0)).to_string() + } + } + + /// Returns a displayable link layer address + pub fn hwaddr(&self) -> Option { + unsafe { + let str_ptr = SCNetworkInterfaceGetHardwareAddressString(self.0); + if str_ptr.is_null() { + None + } else { + Some(CFString::wrap_under_get_rule(str_ptr).to_string()) + } + } + } + + /// Returns the configuration settings associated with this network interface + pub fn config(&self) -> Option { + unsafe { + let config_ptr = SCNetworkInterfaceGetConfiguration(self.0); + if config_ptr.is_null() { + None + } else { + Some(CFDictionary::wrap_under_get_rule(config_ptr)) + } + } + } +} + +impl fmt::Display for SCNetworkInterface { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:?}", self) + } +} + +impl fmt::Debug for SCNetworkInterface { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let mtu = self.mtu(); + let mtu_fmt = if mtu.is_none() { + format!("None") + } else { + let mtu = mtu.unwrap(); + format!( + "{{ current: {}, min: {:?}, max: {:?} }}", + mtu.current, mtu.min, mtu.max + ) + }; + + write!( + f, + "SCNetworkInterface{{ mtu: {}, name: {:?}, type: {:?}, hwaddr: {:?} }}", + mtu_fmt, + self.name(), + self.interface_type(), + self.hwaddr() + ) + } +} + + +#[cfg(all(feature = "nightly", test))] +fn test_network_service() { + use core_foundation::base::kCFAllocatorDefault; + + let prefs = SCPreferences::new(unsafe { kCFAllocatorDefault }, "test_session", None); + let list = SCNetworkService::list(&prefs); + + for network_service in list.iter() { + let network_service_id = network_service.id(); + let res = SCNetworkService::from_id(&prefs, &network_service_id); + + assert!(res.is_some()); + let res = res.unwrap(); + + assert_eq!(res.id(), network_service.id()); + assert_eq!(res.name(), network_service.name()); + } +} diff --git a/system-configuration/src/preferences.rs b/system-configuration/src/preferences.rs new file mode 100644 index 0000000..90f8df0 --- /dev/null +++ b/system-configuration/src/preferences.rs @@ -0,0 +1,49 @@ +// Copyright 2017 Amagicom AB. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Bindings to [`SCPreferences`]. +//! +//! See the examples directory for examples how to use this module. +//! +//! [`SCPreferences`]: https://developer.apple.com/documentation/systemconfiguration/scpreferences-ft8 + + +use core_foundation::base::CFAllocatorRef; +use core_foundation::base::TCFType; +use core_foundation::string::CFString; + +pub use system_configuration_sys::preferences::*; + +use std::ptr; + + +declare_TCFType!{ + /// The handle to an open preferences session for accessing system configuration preferences. + SCPreferences, SCPreferencesRef +} + +impl_TCFType!(SCPreferences, SCPreferencesRef, SCPreferencesGetTypeID); + + +impl SCPreferences { + /// Initiates access to the per-system set of configuration preferences. + pub fn new(allocator: CFAllocatorRef, name: &str, prefs_id: Option<&str>) -> Self { + let prefs_id = match prefs_id { + Some(prefs_id) => CFString::new(prefs_id).as_concrete_TypeRef(), + None => ptr::null(), + }; + + unsafe { + SCPreferences::wrap_under_get_rule(SCPreferencesCreate( + allocator, + CFString::new(name).as_concrete_TypeRef(), + prefs_id, + )) + } + } +}