]> code.octet-stream.net Git - netwatcher/blob - src/watch_win.rs
23b274ea0d35ef3a992fc574ebdf44085423a445
[netwatcher] / src / watch_win.rs
1 use std::ffi::c_void;
2 use std::pin::Pin;
3 use std::sync::Mutex;
4
5 use windows::Win32::Foundation::ERROR_INVALID_HANDLE;
6 use windows::Win32::Foundation::ERROR_INVALID_PARAMETER;
7 use windows::Win32::Foundation::ERROR_NOT_ENOUGH_MEMORY;
8 use windows::Win32::Foundation::NO_ERROR;
9 use windows::Win32::NetworkManagement::IpHelper::CancelMibChangeNotify2;
10 use windows::Win32::NetworkManagement::IpHelper::MIB_NOTIFICATION_TYPE;
11 use windows::Win32::NetworkManagement::IpHelper::MIB_UNICASTIPADDRESS_ROW;
12 use windows::Win32::{
13 Foundation::{BOOLEAN, HANDLE},
14 NetworkManagement::IpHelper::NotifyUnicastIpAddressChange,
15 Networking::WinSock::AF_UNSPEC,
16 };
17
18 use crate::Error;
19 use crate::List;
20 use crate::Update;
21
22 struct WatchState {
23 /// The last result that we captured, for diffing
24 prev_list: List,
25 /// User's callback
26 cb: Box<dyn FnMut(Update) + Send + 'static>,
27 }
28
29 pub(crate) struct WatchHandle {
30 hnd: HANDLE,
31 _state: Pin<Box<Mutex<WatchState>>>,
32 }
33
34 impl Drop for WatchHandle {
35 fn drop(&mut self) {
36 unsafe {
37 let _ = CancelMibChangeNotify2(self.hnd);
38 }
39 }
40 }
41
42 pub(crate) fn watch_interfaces<F: FnMut(Update) + Send + 'static>(
43 callback: F,
44 ) -> Result<WatchHandle, Error> {
45 let state = Box::pin(Mutex::new(WatchState {
46 prev_list: List::default(),
47 cb: Box::new(callback),
48 }));
49 let state_ctx = &*state.as_ref() as *const _ as *const c_void;
50
51 let mut hnd = HANDLE::default();
52 let res = unsafe {
53 NotifyUnicastIpAddressChange(
54 AF_UNSPEC,
55 Some(notif),
56 Some(state_ctx),
57 BOOLEAN(0),
58 &mut hnd,
59 )
60 };
61 match res {
62 NO_ERROR => {
63 // Trigger an initial update.
64 // This is allowed to race with true updates because it
65 // will always calculate a diff and discard no-ops.
66 handle_notif(&mut state.lock().unwrap());
67 // Then return the handle
68 Ok(WatchHandle { hnd, _state: state })
69 }
70 ERROR_INVALID_HANDLE => Err(Error::Internal),
71 ERROR_INVALID_PARAMETER => Err(Error::Internal),
72 ERROR_NOT_ENOUGH_MEMORY => Err(Error::Internal),
73 _ => Err(Error::Internal), // TODO: Use FormatMessage and get real error
74 }
75 }
76
77 unsafe extern "system" fn notif(
78 ctx: *const c_void,
79 _row: *const MIB_UNICASTIPADDRESS_ROW,
80 _notification_type: MIB_NOTIFICATION_TYPE,
81 ) {
82 let state_ptr = ctx as *const Mutex<WatchState>;
83 unsafe {
84 let state_guard = &mut *state_ptr
85 .as_ref()
86 .expect("callback ctx should never be null")
87 .lock()
88 .unwrap();
89 handle_notif(state_guard);
90 }
91 }
92
93 fn handle_notif(state: &mut WatchState) {
94 let Ok(new_list) = crate::list::list_interfaces() else {
95 return;
96 };
97 if new_list == state.prev_list {
98 return;
99 }
100 let update = Update {
101 interfaces: new_list.0.clone(),
102 diff: new_list.diff_from(&state.prev_list),
103 };
104 (state.cb)(update);
105 state.prev_list = new_list;
106 }