diff --git a/README.md b/README.md new file mode 100644 index 0000000..90d5291 --- /dev/null +++ b/README.md @@ -0,0 +1,12 @@ +## SystemConfiguration bindings + +This crate is a high level binding to the Apple [SystemConfiguration] framework. For low level +FFI bindings, check out the [`system-configuration-sys`] crate. + +This crate only implements a small part of the [SystemConfiguration] framework so far. If you +need a yet unimplemented part, feel free to submit a pull request! + +[SystemConfiguration]: https://developer.apple.com/documentation/systemconfiguration?language=objc +[`system-configuration-sys`]: https://crates.io/crates/system-configuration-sys + +License: MIT/Apache-2.0 diff --git a/system-configuration-sys/src/lib.rs b/system-configuration-sys/src/lib.rs index b4304b9..a9ccd3a 100644 --- a/system-configuration-sys/src/lib.rs +++ b/system-configuration-sys/src/lib.rs @@ -6,6 +6,12 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. +//! Low level bindings to the Apple [SystemConfiguration] framework. Generated with bindgen. +//! For a safe, higher level, API, check out the [`system-configuration`] crate. +//! +//! [SystemConfiguration]: https://developer.apple.com/documentation/systemconfiguration?language=objc +//! [`system-configuration`]: https://crates.io/crates/system-configuration + #![allow(non_camel_case_types)] #![allow(non_upper_case_globals)] #![allow(non_snake_case)] diff --git a/system-configuration/Cargo.toml b/system-configuration/Cargo.toml index 60efcd0..ffe77b3 100644 --- a/system-configuration/Cargo.toml +++ b/system-configuration/Cargo.toml @@ -7,6 +7,7 @@ keywords = ["macos", "system", "configuration", "bindings"] categories = ["api-bindings", "os::macos-apis"] repository = "https://github.com/mullvad/system-configuration-rs" license = "MIT/Apache-2.0" +readme = "../README.md" [dependencies] core-foundation = "0.5" diff --git a/system-configuration/examples/set_dns.rs b/system-configuration/examples/set_dns.rs new file mode 100644 index 0000000..07836a2 --- /dev/null +++ b/system-configuration/examples/set_dns.rs @@ -0,0 +1,53 @@ +extern crate system_configuration; + +extern crate core_foundation; + +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}; + +// This example will change the DNS settings on the primary +// network interface to 8.8.8.8 and 8.8.4.4 + +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); +} + +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 + } +} + +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)]) +} diff --git a/system-configuration/examples/watch_dns.rs b/system-configuration/examples/watch_dns.rs new file mode 100644 index 0000000..de60009 --- /dev/null +++ b/system-configuration/examples/watch_dns.rs @@ -0,0 +1,79 @@ +extern crate system_configuration; + +extern crate core_foundation; + +use core_foundation::array::CFArray; +use core_foundation::base::{CFType, TCFType}; +use core_foundation::dictionary::CFDictionary; +use core_foundation::propertylist::CFPropertyList; +use core_foundation::runloop::{CFRunLoop, kCFRunLoopCommonModes}; +use core_foundation::string::CFString; + +use system_configuration::dynamic_store::{SCDynamicStore, SCDynamicStoreBuilder, + SCDynamicStoreCallBackContext}; + +// This example will watch the dynamic store for changes to any DNS setting. As soon as a change +// is detected, it will be printed to stdout. + +fn main() { + let callback_context = SCDynamicStoreCallBackContext { + callout: my_callback, + info: Context { call_count: 0 }, + }; + + let store = SCDynamicStoreBuilder::new("my-watch-dns-store") + .callback_context(callback_context) + .build(); + + let watch_keys: CFArray = CFArray::from_CFTypes(&[]); + let watch_patterns = + CFArray::from_CFTypes(&[CFString::from("(State|Setup):/Network/Service/.*/DNS")]); + + if store.set_notification_keys(&watch_keys, &watch_patterns) { + println!("Registered for notifications"); + } else { + panic!("Unable to register notifications"); + } + + let run_loop_source = store.create_run_loop_source(); + let run_loop = CFRunLoop::get_current(); + run_loop.add_source(&run_loop_source, unsafe { kCFRunLoopCommonModes }); + + println!("Entering run loop"); + CFRunLoop::run_current(); +} + +/// This struct acts as a user provided context/payload to each notification callback. +/// Here one can store any type of data or state needed in the callback function. +#[derive(Debug)] +struct Context { + call_count: u64, +} + +fn my_callback(store: SCDynamicStore, changed_keys: CFArray, context: &mut Context) { + context.call_count += 1; + println!("Callback call count: {}", context.call_count); + + for key in changed_keys.iter() { + if let Some(addresses) = get_dns(&store, key.clone()) { + let addresses = addresses.iter().map(|s| s.to_string()).collect::>(); + println!("{} changed DNS to {:?}", *key, addresses); + } else { + println!("{} removed DNS", *key); + } + } +} + +fn get_dns(store: &SCDynamicStore, path: CFString) -> Option> { + let dictionary = store + .get(path) + .and_then(CFPropertyList::downcast_into::); + if let Some(dictionary) = dictionary { + dictionary + .find2(&CFString::from_static_string("ServerAddresses")) + .map(|ptr| unsafe { CFType::wrap_under_get_rule(ptr) }) + .and_then(CFType::downcast_into::>) + } else { + None + } +} diff --git a/system-configuration/src/bin/set_dns.rs b/system-configuration/src/bin/set_dns.rs deleted file mode 100644 index 1556e5b..0000000 --- a/system-configuration/src/bin/set_dns.rs +++ /dev/null @@ -1,44 +0,0 @@ -extern crate system_configuration; - -extern crate core_foundation; - -use core_foundation::array::CFArray; -use core_foundation::base::TCFType; -use core_foundation::dictionary::CFDictionary; -use core_foundation::string::{CFString, CFStringRef}; - -use system_configuration::dynamic_store::SCDynamicStoreBuilder; - -fn main() { - unsafe { - let store = SCDynamicStoreBuilder::new("my-test-dyn-store").build(); - println!("Created dynamic store"); - - let ipv4_dict = store - .get("State:/Network/Global/IPv4") - .expect("Unable to find global settings") - .downcast_into::() - .expect("Global IPv4 settings not a dictionary"); - println!("Got IPv4 global property list"); - - let pri_service_id_ptr = ipv4_dict - .find2(&CFString::from_static_string("PrimaryService")) - .expect("No PrimaryService"); - let pri_service_id = CFString::wrap_under_get_rule(pri_service_id_ptr as CFStringRef); - - let pri_service_path = - CFString::new(&format!("State:/Network/Service/{}/DNS", pri_service_id)); - println!("PrimaryService path: {}", pri_service_path); - - let server_addresses_key = CFString::from_static_string("ServerAddresses"); - let server_addresses_value = CFArray::from_CFTypes(&[ - CFString::from_static_string("8.8.8.8"), - CFString::from_static_string("8.8.4.4"), - ]); - let dns_dictionary = - CFDictionary::from_CFType_pairs(&[(server_addresses_key, server_addresses_value)]); - - let success = store.set(pri_service_path, dns_dictionary); - println!("success? {}", success); - } -} diff --git a/system-configuration/src/bin/watch_dns.rs b/system-configuration/src/bin/watch_dns.rs deleted file mode 100644 index 68a107e..0000000 --- a/system-configuration/src/bin/watch_dns.rs +++ /dev/null @@ -1,82 +0,0 @@ -extern crate system_configuration; - -extern crate core_foundation; - -use core_foundation::array::CFArray; -use core_foundation::dictionary::CFDictionary; -use core_foundation::runloop::{CFRunLoop, kCFRunLoopCommonModes}; -use core_foundation::string::CFString; - -use system_configuration::dynamic_store::{SCDynamicStore, SCDynamicStoreBuilder, - SCDynamicStoreCallBackContext}; - -use std::env; - -#[derive(Debug)] -struct Payload { - i: u64, - service_path: CFString, -} - -impl Drop for Payload { - fn drop(&mut self) { - println!("Payload Drop"); - } -} - -fn main() { - let service_id = env::args() - .skip(1) - .next() - .expect("Give service uuid as first argument"); - let service_path = CFString::from(&format!("State:/Network/Service/{}/DNS", service_id)[..]); - println!("Watching {}", service_path); - - let watch_keys = CFArray::from_CFTypes(&[service_path.clone()]); - let watch_patterns: CFArray = CFArray::from_CFTypes(&[]); - - let callback_context = SCDynamicStoreCallBackContext { - callout: my_callback, - info: Payload { - i: 0, - service_path: service_path, - }, - }; - - let store = SCDynamicStoreBuilder::new("my-watch-dns-store") - .callback_context(callback_context) - .build(); - println!("Created dynamic store"); - - if store.set_notification_keys(&watch_keys, &watch_patterns) { - println!("Registered for notifications"); - } else { - panic!("Unable to register notifications"); - } - let run_loop_source = store.create_run_loop_source(); - - let run_loop = CFRunLoop::get_current(); - run_loop.add_source(&run_loop_source, unsafe { kCFRunLoopCommonModes }); - println!("Entering run loop"); - CFRunLoop::run_current(); -} - -fn my_callback(store: SCDynamicStore, _changed_keys: CFArray, payload: &mut Payload) { - println!("my_callback2 (payload: {:?})", payload); - payload.i += 1; - - if payload.i > 1 { - // Only reset DNS on first callback for now. To not get stuck in infinite loop. - return; - } - - let server_addresses_key = CFString::from_static_string("ServerAddresses"); - let server_address_1 = CFString::from_static_string("192.168.1.1"); - let server_addresses_value = CFArray::from_CFTypes(&[server_address_1]); - - let dns_dictionary = - CFDictionary::from_CFType_pairs(&[(server_addresses_key, server_addresses_value)]); - - let success = store.set(payload.service_path.clone(), dns_dictionary); - println!("callback: {}", success); -} diff --git a/system-configuration/src/dynamic_store.rs b/system-configuration/src/dynamic_store.rs index 8882435..d3f785b 100644 --- a/system-configuration/src/dynamic_store.rs +++ b/system-configuration/src/dynamic_store.rs @@ -6,6 +6,12 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. +//! Bindings to [`SCDynamicStore`]. +//! +//! See the examples directory for examples how to use this module. +//! +//! [`SCDynamicStore`]: https://developer.apple.com/documentation/systemconfiguration/scdynamicstore?language=objc + use core_foundation::array::{CFArray, CFArrayRef}; use core_foundation::base::{TCFType, kCFAllocatorDefault}; use core_foundation::boolean::CFBoolean; diff --git a/system-configuration/src/lib.rs b/system-configuration/src/lib.rs index c21cca8..f0234d1 100644 --- a/system-configuration/src/lib.rs +++ b/system-configuration/src/lib.rs @@ -6,6 +6,17 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. +//! # SystemConfiguration bindings +//! +//! This crate is a high level binding to the Apple [SystemConfiguration] framework. For low level +//! FFI bindings, check out the [`system-configuration-sys`] crate. +//! +//! This crate only implements a small part of the [SystemConfiguration] framework so far. If you +//! need a yet unimplemented part, feel free to submit a pull request! +//! +//! [SystemConfiguration]: https://developer.apple.com/documentation/systemconfiguration?language=objc +//! [`system-configuration-sys`]: https://crates.io/crates/system-configuration-sys + #[macro_use] extern crate core_foundation; extern crate system_configuration_sys;