watch_dns/
watch_dns.rs

1use core_foundation::{
2    array::CFArray,
3    base::{CFType, TCFType, ToVoid},
4    dictionary::CFDictionary,
5    propertylist::CFPropertyList,
6    runloop::{kCFRunLoopCommonModes, CFRunLoop},
7    string::CFString,
8};
9use system_configuration::{
10    dynamic_store::{SCDynamicStore, SCDynamicStoreBuilder, SCDynamicStoreCallBackContext},
11    sys::schema_definitions::kSCPropNetDNSServerAddresses,
12};
13
14// This example will watch the dynamic store for changes to any DNS setting. As soon as a change
15// is detected, it will be printed to stdout.
16
17fn main() {
18    let callback_context = SCDynamicStoreCallBackContext {
19        callout: my_callback,
20        info: Context { call_count: 0 },
21    };
22
23    let store = SCDynamicStoreBuilder::new("my-watch-dns-store")
24        .callback_context(callback_context)
25        .build();
26
27    let watch_keys: CFArray<CFString> = CFArray::from_CFTypes(&[]);
28    let watch_patterns =
29        CFArray::from_CFTypes(&[CFString::from("(State|Setup):/Network/Service/.*/DNS")]);
30
31    if store.set_notification_keys(&watch_keys, &watch_patterns) {
32        println!("Registered for notifications");
33    } else {
34        panic!("Unable to register notifications");
35    }
36
37    let run_loop_source = store.create_run_loop_source();
38    let run_loop = CFRunLoop::get_current();
39    run_loop.add_source(&run_loop_source, unsafe { kCFRunLoopCommonModes });
40
41    println!("Entering run loop");
42    CFRunLoop::run_current();
43}
44
45/// This struct acts as a user provided context/payload to each notification callback.
46/// Here one can store any type of data or state needed in the callback function.
47#[derive(Debug)]
48struct Context {
49    call_count: u64,
50}
51
52#[allow(clippy::needless_pass_by_value)]
53fn my_callback(store: SCDynamicStore, changed_keys: CFArray<CFString>, context: &mut Context) {
54    context.call_count += 1;
55    println!("Callback call count: {}", context.call_count);
56
57    for key in changed_keys.iter() {
58        if let Some(addresses) = get_dns(&store, key.clone()) {
59            println!("{} changed DNS to {:?}", *key, addresses);
60        } else {
61            println!("{} removed DNS", *key);
62        }
63    }
64}
65
66fn get_dns(store: &SCDynamicStore, path: CFString) -> Option<Vec<String>> {
67    let dns_settings = store
68        .get(path)
69        .and_then(CFPropertyList::downcast_into::<CFDictionary>)?;
70    let address_array = dns_settings
71        .find(unsafe { kSCPropNetDNSServerAddresses }.to_void())
72        .map(|ptr| unsafe { CFType::wrap_under_get_rule(*ptr) })
73        .and_then(CFType::downcast_into::<CFArray>)?;
74    let mut result = Vec::with_capacity(address_array.len() as usize);
75    for address_ptr in &address_array {
76        let address =
77            unsafe { CFType::wrap_under_get_rule(*address_ptr) }.downcast_into::<CFString>()?;
78        result.push(address.to_string())
79    }
80    Some(result)
81}