From 3981aee385baaa99dde1942e988bc3f1207bf06d Mon Sep 17 00:00:00 2001 From: Eduardo Trujillo <ed@chromabits.com> Date: Sun, 2 Jan 2022 14:16:06 -0800 Subject: [PATCH] Add support for connectivity state and state change events This can be used to support situations where a Captive Portal is present --- src/action.rs | 6 +- src/condition.rs | 155 +++++++++++++++--------- src/dbus_wrappers/connection.rs | 5 +- src/dbus_wrappers/connectivity_state.rs | 13 ++ src/dbus_wrappers/device.rs | 2 +- src/dbus_wrappers/manager.rs | 29 ++++- src/dbus_wrappers/mod.rs | 4 +- src/dbus_wrappers/state.rs | 16 +++ src/event.rs | 46 +++++-- src/identifier.rs | 15 ++- src/watcher.rs | 21 ++++ 11 files changed, 223 insertions(+), 89 deletions(-) create mode 100644 src/dbus_wrappers/connectivity_state.rs create mode 100644 src/dbus_wrappers/state.rs diff --git a/src/action.rs b/src/action.rs index 868a093..840561c 100644 --- a/src/action.rs +++ b/src/action.rs @@ -63,11 +63,7 @@ impl Action { }?; let maybe_device = match device_identifier { - Some(device_identifier) => { - let identifier = device_identifier.into_device(conn).await?; - - Some(identifier) - } + Some(device_identifier) => device_identifier.into_device(conn).await?, None => None, }; diff --git a/src/condition.rs b/src/condition.rs index ff14928..ec7f369 100644 --- a/src/condition.rs +++ b/src/condition.rs @@ -2,13 +2,13 @@ use std::{collections::HashSet, sync::Arc}; use anyhow::Result; use dbus::nonblock::SyncConnection; -use futures::{future::join_all, stream, StreamExt}; +use futures::{stream, StreamExt}; use serde_derive::{Deserialize, Serialize}; use crate::{ dbus_wrappers::{ - active_connection::ActiveConnectionWrapper, device::DeviceWrapper, - device_state::DeviceState, + active_connection::ActiveConnectionWrapper, connectivity_state::ConnectivityState, + device::DeviceWrapper, device_state::DeviceState, manager::ManagerWrapper, state::State, }, identifier::{ActiveConnectionIdentifier, DeviceIdentifier}, }; @@ -20,6 +20,10 @@ pub enum Condition { AlwaysTrue, // Always fails. AlwaysFalse, + // Passes if the last connectivity check is any of the specificed states. + ConnectivityStateIsAnyOf { + states: HashSet<ConnectivityState>, + }, // Passes if the device has any active connections. DeviceIsConnected { device_identifier: DeviceIdentifier, @@ -53,6 +57,11 @@ pub enum Condition { // device_identifier: DeviceIdentifier, // ssids: Vec<String>, // }, + // Passes if the current NetworkManager state is one of the specified + // states. + StateIsAnyOf { + states: HashSet<State>, + }, } impl Condition { @@ -60,15 +69,28 @@ impl Condition { match self { Condition::AlwaysTrue => Ok(true), Condition::AlwaysFalse => Ok(false), + Condition::ConnectivityStateIsAnyOf { states } => { + log::debug!("Evaluating ConnectivityStateIsAnyOf condition"); + + let manager = ManagerWrapper::from_connection(conn).await; + + Ok(states.contains(&manager.get_last_connectivity_state().await?)) + } Condition::DeviceIsConnected { device_identifier: device_id, } => { log::debug!("Evaluating DeviceIsConnected condition for {:?}", device_id); - let device = device_id.into_device(conn).await?; - - device_matches_states(&device, vec![DeviceState::Activated].into_iter().collect()) - .await + match device_id.into_device(conn).await? { + Some(device) => { + device_matches_states( + &device, + vec![DeviceState::Activated].into_iter().collect(), + ) + .await + } + None => Ok(false), + } } Condition::DeviceIsNotConnected { device_identifier: device_id, @@ -78,15 +100,18 @@ impl Condition { device_id ); - let device = device_id.into_device(conn).await?; - - device_matches_states( - &device, - vec![DeviceState::Disconnected, DeviceState::Unavailable] - .into_iter() - .collect(), - ) - .await + match device_id.into_device(conn).await? { + Some(device) => { + device_matches_states( + &device, + vec![DeviceState::Disconnected, DeviceState::Unavailable] + .into_iter() + .collect(), + ) + .await + } + None => Ok(true), + } } Condition::DeviceStateIsAnyOf { device_identifier: device_id, @@ -97,9 +122,10 @@ impl Condition { device_id ); - let device = device_id.into_device(conn).await?; - - device_matches_states(&device, states.clone()).await + match device_id.into_device(conn).await? { + Some(device) => device_matches_states(&device, states.clone()).await, + None => Ok(false), + } } Condition::DeviceIsConnectedToOneOf { device_identifier: device_id, @@ -123,7 +149,13 @@ impl Condition { Ok(!device_is_connected_to_one_of(conn, device_id, connections).await?) } - _ => Ok(false), + Condition::StateIsAnyOf { states } => { + log::debug!("Evaluating StateIsAnyOf condition"); + + let manager = ManagerWrapper::from_connection(conn).await; + + Ok(states.contains(&manager.get_state().await?)) + } } } } @@ -143,46 +175,49 @@ async fn device_is_connected_to_one_of<'a>( device_identifier: &DeviceIdentifier, connections: &'a Vec<ActiveConnectionIdentifier>, ) -> Result<bool> { - let device = device_identifier.into_device(conn).await?; - - let active_connections: Vec<ActiveConnectionWrapper<'a>> = stream::iter(connections) - .filter_map(|connection_identifier| async move { - match connection_identifier.into_active_connection(conn).await { - Ok(active_connection) => active_connection, - Err(err) => { - log::error!( - "Got error retrieving active connection for {:?}: {:?}", - connection_identifier, - err - ); - - None - } - } - }) - .collect() - .await; - - let has_any_matches = stream::iter(active_connections).any(|active_connection| { - let device_path = device.get_path(); - - async move { - let device_paths = active_connection.get_device_paths().await; - - match device_paths { - Ok(device_paths) => device_paths.iter().any(|x| x.eq(&device_path)), - Err(err) => { - log::error!( - "Unable to get device paths for active connection {:?}: {:?}", - active_connection.get_path(), - err - ); - - false + match device_identifier.into_device(conn).await? { + Some(device) => { + let active_connections: Vec<ActiveConnectionWrapper<'a>> = stream::iter(connections) + .filter_map(|connection_identifier| async move { + match connection_identifier.into_active_connection(conn).await { + Ok(active_connection) => active_connection, + Err(err) => { + log::error!( + "Got error retrieving active connection for {:?}: {:?}", + connection_identifier, + err + ); + + None + } + } + }) + .collect() + .await; + + let has_any_matches = stream::iter(active_connections).any(|active_connection| { + let device_path = device.get_path(); + + async move { + let device_paths = active_connection.get_device_paths().await; + + match device_paths { + Ok(device_paths) => device_paths.iter().any(|x| x.eq(&device_path)), + Err(err) => { + log::error!( + "Unable to get device paths for active connection {:?}: {:?}", + active_connection.get_path(), + err + ); + + false + } + } } - } - } - }); + }); - Ok(has_any_matches.await) + Ok(has_any_matches.await) + } + None => Ok(false), + } } diff --git a/src/dbus_wrappers/connection.rs b/src/dbus_wrappers/connection.rs index f56cea4..1b83b3b 100644 --- a/src/dbus_wrappers/connection.rs +++ b/src/dbus_wrappers/connection.rs @@ -1,11 +1,8 @@ use std::{sync::Arc, time::Duration}; -use anyhow::Result; use dbus::{Path, nonblock::{Proxy, SyncConnection}}; -use crate::dbus_codegen::{network_manager_connection_active::{OrgFreedesktopNetworkManagerConnectionActive, OrgFreedesktopNetworkManagerConnectionActiveStateChanged}}; - -use super::signal::SignalStreamWrapper; +use crate::dbus_codegen::{network_manager_connection_active::{OrgFreedesktopNetworkManagerConnectionActive}}; pub struct ConnectionWrapper<'a> { conn: Arc<SyncConnection>, diff --git a/src/dbus_wrappers/connectivity_state.rs b/src/dbus_wrappers/connectivity_state.rs new file mode 100644 index 0000000..cce00a6 --- /dev/null +++ b/src/dbus_wrappers/connectivity_state.rs @@ -0,0 +1,13 @@ +use num_derive::FromPrimitive; +use serde_derive::{Deserialize, Serialize}; + +// NMConnectivityState +#[derive(FromPrimitive, PartialEq, Eq, Hash, Serialize, Deserialize, Clone, Debug)] +#[repr(u32)] +pub enum ConnectivityState { + Unknown = 0, + None = 1, + Portal = 2, + Limited = 3, + Full = 4, +} \ No newline at end of file diff --git a/src/dbus_wrappers/device.rs b/src/dbus_wrappers/device.rs index 84579d7..43a2b8a 100644 --- a/src/dbus_wrappers/device.rs +++ b/src/dbus_wrappers/device.rs @@ -1,4 +1,4 @@ -use std::{convert::TryFrom, sync::Arc, time::Duration}; +use std::{sync::Arc, time::Duration}; use anyhow::Result; use dbus::{ diff --git a/src/dbus_wrappers/manager.rs b/src/dbus_wrappers/manager.rs index c45872a..05dcee9 100644 --- a/src/dbus_wrappers/manager.rs +++ b/src/dbus_wrappers/manager.rs @@ -1,20 +1,21 @@ use std::{sync::Arc, time::Duration}; +use anyhow::Result; use dbus::{ - nonblock::{Proxy, SyncConnection}, + nonblock::{stdintf::org_freedesktop_dbus::PropertiesPropertiesChanged, Proxy, SyncConnection}, Path, }; use futures::{future::join_all, stream, StreamExt}; +use num_traits::FromPrimitive; use crate::dbus_codegen::network_manager::{ OrgFreedesktopNetworkManager, OrgFreedesktopNetworkManagerDeviceAdded, OrgFreedesktopNetworkManagerDeviceRemoved, }; -use super::{ - active_connection::ActiveConnectionWrapper, connection::ConnectionWrapper, - device::DeviceWrapper, signal::SignalStreamWrapper, -}; +use super::{active_connection::ActiveConnectionWrapper, connection::ConnectionWrapper, connectivity_state::ConnectivityState, device::DeviceWrapper, signal::SignalStreamWrapper, state::State}; + +const PATH: &'static str = "/org/freedesktop/NetworkManager"; pub struct ManagerWrapper<'a> { conn: Arc<SyncConnection>, @@ -27,7 +28,7 @@ impl<'a> ManagerWrapper<'a> { let proxy: Proxy<'a, Arc<SyncConnection>> = Proxy::new( "org.freedesktop.NetworkManager", - "/org/freedesktop/NetworkManager", + PATH, Duration::from_millis(5000), inner_conn, ); @@ -102,6 +103,16 @@ impl<'a> ManagerWrapper<'a> { Ok(matches.pop()) } + pub async fn get_state(&self) -> Result<State> { + Ok(FromPrimitive::from_u32(self.inner.state().await?) + .unwrap_or(State::Unknown)) + } + + pub async fn get_last_connectivity_state(&self) -> Result<ConnectivityState> { + Ok(FromPrimitive::from_u32(self.inner.connectivity().await?) + .unwrap_or(ConnectivityState::Unknown)) + } + pub async fn activate_connection( &self, connection: Option<&'a ConnectionWrapper<'a>>, @@ -145,4 +156,10 @@ impl<'a> ManagerWrapper<'a> { ) -> anyhow::Result<SignalStreamWrapper<OrgFreedesktopNetworkManagerDeviceRemoved>> { SignalStreamWrapper::from_match_rule(&self.conn, None, None).await } + + pub async fn properties_changed_signal_stream( + &self, + ) -> anyhow::Result<SignalStreamWrapper<PropertiesPropertiesChanged>> { + SignalStreamWrapper::from_match_rule(&self.conn, None, Some(Path::from(PATH))).await + } } diff --git a/src/dbus_wrappers/mod.rs b/src/dbus_wrappers/mod.rs index 6c9a830..c0359da 100644 --- a/src/dbus_wrappers/mod.rs +++ b/src/dbus_wrappers/mod.rs @@ -1,7 +1,9 @@ pub mod active_connection; pub mod connection; +pub mod connectivity_state; pub mod device; pub mod device_state; pub mod manager; pub mod manager_settings; -pub mod signal; \ No newline at end of file +pub mod signal; +pub mod state; \ No newline at end of file diff --git a/src/dbus_wrappers/state.rs b/src/dbus_wrappers/state.rs new file mode 100644 index 0000000..81852d8 --- /dev/null +++ b/src/dbus_wrappers/state.rs @@ -0,0 +1,16 @@ +use num_derive::FromPrimitive; +use serde_derive::{Deserialize, Serialize}; + +// NMState +#[derive(FromPrimitive, PartialEq, Eq, Hash, Serialize, Deserialize, Clone, Debug)] +#[repr(u32)] +pub enum State { + Unknown = 0, + Asleep = 10, + Disconnected = 20, + Disconnecting = 30, + Connecting = 40, + ConnectedLocal = 50, + ConnectedSite = 60, + ConnectedGlobal = 70, +} \ No newline at end of file diff --git a/src/event.rs b/src/event.rs index 5f3c68c..7101565 100644 --- a/src/event.rs +++ b/src/event.rs @@ -1,4 +1,4 @@ -use std::sync::Arc; +use std::{collections::HashSet, sync::Arc}; use anyhow::Result; use dbus::nonblock::SyncConnection; @@ -8,15 +8,31 @@ use tokio::{ task::JoinHandle, }; -use crate::{config::Config, identifier::DeviceIdentifier}; +use crate::{config::Config, dbus_wrappers::{connectivity_state::ConnectivityState, state::State}, identifier::DeviceIdentifier}; #[derive(Debug)] pub enum Event { - DeviceAdded { device_path: dbus::Path<'static> }, - DeviceRemoved { device_path: dbus::Path<'static> }, - DeviceActivated { device_path: dbus::Path<'static> }, - DeviceDeactivating { device_path: dbus::Path<'static> }, - DeviceDisconnected { device_path: dbus::Path<'static> }, + ConnectivityStateChanged { + connectivity_state: ConnectivityState, + }, + DeviceAdded { + device_path: dbus::Path<'static>, + }, + DeviceRemoved { + device_path: dbus::Path<'static>, + }, + DeviceActivated { + device_path: dbus::Path<'static>, + }, + DeviceDeactivating { + device_path: dbus::Path<'static>, + }, + DeviceDisconnected { + device_path: dbus::Path<'static>, + }, + StateChanged { + state: State, + }, } pub async fn handle_events( @@ -45,7 +61,7 @@ pub async fn handle_events( log::warn!("Got an empty event"); } } - + }, _ = stop_signal_rx.recv() => { log::info!("Stoping event handler task"); @@ -64,6 +80,9 @@ pub async fn handle_events( #[derive(Serialize, Deserialize, Clone, Debug)] #[serde(tag = "type")] pub enum Trigger { + ConnectivityStateChanged { + states: HashSet<ConnectivityState>, + }, DeviceAdded { device_identifier: Option<DeviceIdentifier>, }, @@ -79,11 +98,18 @@ pub enum Trigger { DeviceDisconnected { device_identifier: Option<DeviceIdentifier>, }, + StateChanged { + states: HashSet<State>, + }, } impl Trigger { pub async fn matches_event(&self, conn: &Arc<SyncConnection>, event: &Event) -> Result<bool> { match (event, self) { + ( + Event::ConnectivityStateChanged { connectivity_state }, + Trigger::ConnectivityStateChanged { states }, + ) => Ok(states.contains(connectivity_state)), (Event::DeviceAdded { device_path }, Trigger::DeviceAdded { device_identifier }) => { match device_identifier { Some(device_identified) => { @@ -138,6 +164,10 @@ impl Trigger { } None => Ok(true), }, + ( + Event::StateChanged { state }, + Trigger::StateChanged { states }, + ) => Ok(states.contains(state)), _ => Ok(false), } } diff --git a/src/identifier.rs b/src/identifier.rs index 869d6d0..823429a 100644 --- a/src/identifier.rs +++ b/src/identifier.rs @@ -33,12 +33,19 @@ impl DeviceIdentifier { } } - pub async fn into_device(&self, conn: &Arc<SyncConnection>) -> Result<DeviceWrapper<'static>> { - let device_path = self.into_path(conn).await?; + pub async fn into_device(&self, conn: &Arc<SyncConnection>) -> Result<Option<DeviceWrapper<'static>>> { + match self.into_path(conn).await { + Ok(device_path) => { + let device = DeviceWrapper::from_path(conn.clone(), device_path).await; - let device = DeviceWrapper::from_path(conn.clone(), device_path).await; + Ok(Some(device)) + } + Err(err) => { + log::warn!("Unable to resolve device identifier: {:?} {:?}", self, err); - Ok(device) + Ok(None) + }, + } } } diff --git a/src/watcher.rs b/src/watcher.rs index c5f4246..c05ef28 100644 --- a/src/watcher.rs +++ b/src/watcher.rs @@ -25,6 +25,7 @@ pub async fn watch(conn: &Arc<SyncConnection>, mut stop_signal_rx: broadcast::Re let mut device_added_signal = manager.device_added_signal_stream().await?; let mut device_removed_signal = manager.device_removed_signal_stream().await?; + let mut properties_changed_signal = manager.properties_changed_signal_stream().await?; log::debug!("Looking for existing devices"); @@ -68,6 +69,25 @@ pub async fn watch(conn: &Arc<SyncConnection>, mut stop_signal_rx: broadcast::Re watched_devices.remove(&signal.device_path); } }, + Some((msg, signal)) = properties_changed_signal.next() => { + log::debug!("Got PropertiesChanged signal for {}: {:?}", signal.interface_name, &msg); + + if signal.interface_name != "org.freedesktop.NetworkManager" { + break; + } + + if signal.changed_properties.contains_key("Connectivity") { + event_tx.send(Event::ConnectivityStateChanged { + connectivity_state: manager.get_last_connectivity_state().await?, + }).await?; + } + + if signal.changed_properties.contains_key("State") { + event_tx.send(Event::StateChanged { + state: manager.get_state().await?, + }).await?; + } + } _ = stop_signal_rx.recv() => { log::info!("Stoping NetworkManager watcher task"); @@ -80,6 +100,7 @@ pub async fn watch(conn: &Arc<SyncConnection>, mut stop_signal_rx: broadcast::Re device_added_signal.dispose().await?; device_removed_signal.dispose().await?; + properties_changed_signal.dispose().await?; log::debug!("Stopping any remaining child tasks"); -- GitLab