diff --git a/rust/agama-network/src/action.rs b/rust/agama-network/src/action.rs index fc6348ce8c..4706968005 100644 --- a/rust/agama-network/src/action.rs +++ b/rust/agama-network/src/action.rs @@ -54,6 +54,10 @@ pub enum Action { GetConnections(Responder>), /// Gets all scanned access points GetAccessPoints(Responder>), + /// Adds a new access point. + AddAccessPoint(Box), + /// Removes an access point by its hardware address. + RemoveAccessPoint(String), /// Adds a new device. AddDevice(Box), /// Updates a device by its `name`. @@ -66,7 +70,7 @@ pub enum Action { GetDevices(Responder>), GetGeneralState(Responder), /// Connection state changed - ChangeConnectionState(String, ConnectionState), + ChangeConnectionState(Uuid, ConnectionState), /// Persists existing connections if none exist and the network copy is not disabled. ProposeDefault(Responder>), // Copies persistent connections to the target system @@ -77,8 +81,8 @@ pub enum Action { UpdateGeneralState(GeneralState), /// Forces a wireless networks scan refresh RefreshScan(Responder>), - /// Remove the connection with the given ID. - RemoveConnection(String), + /// Remove the connection with the given UUID. + RemoveConnection(Uuid), /// Apply the current configuration. Apply(Responder>), } diff --git a/rust/agama-network/src/error.rs b/rust/agama-network/src/error.rs index c539b4f825..651f001aee 100644 --- a/rust/agama-network/src/error.rs +++ b/rust/agama-network/src/error.rs @@ -40,6 +40,8 @@ pub enum NetworkStateError { CannotUpdateConnection(String), #[error("Unknown device '{0}'")] UnknownDevice(String), + #[error("Unknown access point '{0}'")] + UnknownAccessPoint(String), #[error("Invalid connection UUID: '{0}'")] InvalidUuid(String), #[error("Invalid IP address: '{0}'")] diff --git a/rust/agama-network/src/model.rs b/rust/agama-network/src/model.rs index bb5ae8be41..2234c9686c 100644 --- a/rust/agama-network/src/model.rs +++ b/rust/agama-network/src/model.rs @@ -296,12 +296,10 @@ impl NetworkState { /// Updates a connection with a new one. /// - /// It uses the `id` to decide which connection to update. - /// - /// Additionally, it registers the connection to be removed when the changes are applied. + /// It uses the `uuid` to decide which connection to update. pub fn update_connection(&mut self, conn: Connection) -> Result<(), NetworkStateError> { - let Some(old_conn) = self.get_connection_mut(&conn.id) else { - return Err(NetworkStateError::UnknownConnection(conn.id.clone())); + let Some(old_conn) = self.get_connection_by_uuid_mut(conn.uuid) else { + return Err(NetworkStateError::UnknownConnection(conn.uuid.to_string())); }; *old_conn = conn; @@ -311,9 +309,9 @@ impl NetworkState { /// Removes a connection from the state. /// /// Additionally, it registers the connection to be removed when the changes are applied. - pub fn remove_connection(&mut self, id: &str) -> Result<(), NetworkStateError> { - let Some(position) = self.connections.iter().position(|d| d.id == id) else { - return Err(NetworkStateError::UnknownConnection(id.to_string())); + pub fn remove_connection(&mut self, uuid: Uuid) -> Result<(), NetworkStateError> { + let Some(position) = self.connections.iter().position(|d| d.uuid == uuid) else { + return Err(NetworkStateError::UnknownConnection(uuid.to_string())); }; self.connections.remove(position); @@ -343,6 +341,34 @@ impl NetworkState { Ok(()) } + pub fn add_access_point(&mut self, ap: AccessPoint) -> Result<(), NetworkStateError> { + if let Some(position) = self + .access_points + .iter() + .position(|a| a.hw_address == ap.hw_address) + { + self.access_points.remove(position); + } + self.access_points.push(ap); + + Ok(()) + } + + pub fn remove_access_point(&mut self, hw_address: &str) -> Result<(), NetworkStateError> { + let Some(position) = self + .access_points + .iter() + .position(|a| a.hw_address == hw_address) + else { + return Err(NetworkStateError::UnknownAccessPoint( + hw_address.to_string(), + )); + }; + + self.access_points.remove(position); + Ok(()) + } + /// Sets a controller's ports. /// /// If the connection is not a controller, returns an error. @@ -417,22 +443,23 @@ mod tests { #[test] fn test_update_connection() { let mut state = NetworkState::default(); + let uuid = Uuid::new_v4(); let conn0 = Connection { id: "eth0".to_string(), - uuid: Uuid::new_v4(), + uuid, ..Default::default() }; state.add_connection(conn0).unwrap(); - let uuid = Uuid::new_v4(); let conn1 = Connection { id: "eth0".to_string(), uuid, + firewall_zone: Some("public".to_string()), ..Default::default() }; state.update_connection(conn1).unwrap(); - let found = state.get_connection("eth0").unwrap(); - assert_eq!(found.uuid, uuid); + let found = state.get_connection_by_uuid(uuid).unwrap(); + assert_eq!(found.firewall_zone, Some("public".to_string())); } #[test] @@ -446,20 +473,77 @@ mod tests { #[test] fn test_remove_connection() { let mut state = NetworkState::default(); - let conn0 = Connection::new("eth0".to_string(), DeviceType::Ethernet); + let uuid = Uuid::new_v4(); + let conn0 = Connection { + id: "eth0".to_string(), + uuid, + ..Default::default() + }; state.add_connection(conn0).unwrap(); - state.remove_connection("eth0".as_ref()).unwrap(); - let found = state.get_connection("eth0"); + state.remove_connection(uuid).unwrap(); + let found = state.get_connection_by_uuid(uuid); assert!(found.is_none()); } #[test] fn test_remove_unknown_connection() { let mut state = NetworkState::default(); - let error = state.remove_connection("unknown".as_ref()).unwrap_err(); + let uuid = Uuid::new_v4(); + let error = state.remove_connection(uuid).unwrap_err(); assert!(matches!(error, NetworkStateError::UnknownConnection(_))); } + #[test] + fn test_remove_device() { + let mut state = NetworkState::default(); + let device = Device { + name: "eth0".to_string(), + ..Default::default() + }; + state.add_device(device).unwrap(); + state.remove_device("eth0").unwrap(); + assert!(state.get_device("eth0").is_none()); + } + + #[test] + fn test_add_access_point() { + let mut state = NetworkState::default(); + let ap = AccessPoint { + hw_address: "AA:BB:CC:DD:EE:FF".to_string(), + ssid: SSID(b"test".to_vec()), + ..Default::default() + }; + state.add_access_point(ap.clone()).unwrap(); + assert_eq!(state.access_points.len(), 1); + assert_eq!(state.access_points[0].hw_address, "AA:BB:CC:DD:EE:FF"); + + // Adding same AP should replace it (in our implementation we remove and push) + let mut ap2 = ap.clone(); + ap2.strength = 80; + state.add_access_point(ap2).unwrap(); + assert_eq!(state.access_points.len(), 1); + assert_eq!(state.access_points[0].strength, 80); + } + + #[test] + fn test_remove_access_point() { + let mut state = NetworkState::default(); + let ap = AccessPoint { + hw_address: "AA:BB:CC:DD:EE:FF".to_string(), + ..Default::default() + }; + state.add_access_point(ap).unwrap(); + state.remove_access_point("AA:BB:CC:DD:EE:FF").unwrap(); + assert_eq!(state.access_points.len(), 0); + } + + #[test] + fn test_remove_unknown_access_point() { + let mut state = NetworkState::default(); + let error = state.remove_access_point("unknown").unwrap_err(); + assert!(matches!(error, NetworkStateError::UnknownAccessPoint(_))); + } + #[test] fn test_is_loopback() { let conn = Connection::new("eth0".to_string(), DeviceType::Ethernet); @@ -1726,7 +1810,7 @@ pub struct TunConfig { #[serde(rename_all = "camelCase")] pub enum NetworkChange { ConnectionAdded(Connection), - ConnectionRemoved(String), + ConnectionRemoved(Uuid), /// A new device has been added. DeviceAdded(Device), /// A device has been removed. @@ -1737,9 +1821,13 @@ pub enum NetworkChange { DeviceUpdated(String, Device), /// A connection state has changed. ConnectionStateChanged { - id: String, + uuid: Uuid, state: ConnectionState, }, + /// A new access point has been added. + AccessPointAdded(AccessPoint), + /// An access point has been removed. + AccessPointRemoved(String), } #[derive(Default, Debug, PartialEq, Clone, Deserialize, Serialize, utoipa::ToSchema)] diff --git a/rust/agama-network/src/nm/builder.rs b/rust/agama-network/src/nm/builder.rs index 129288333d..6261917c15 100644 --- a/rust/agama-network/src/nm/builder.rs +++ b/rust/agama-network/src/nm/builder.rs @@ -25,9 +25,12 @@ use crate::{ nm::{ dbus::connection_from_dbus, model::NmDeviceType, - proxies::{ConnectionProxy, DeviceProxy, IP4ConfigProxy, IP6ConfigProxy}, + proxies::{AccessPointProxy, ConnectionProxy, DeviceProxy, IP4ConfigProxy, IP6ConfigProxy}, + }, + types::{ + AccessPoint, ConnectionFlags, Device, DeviceState, DeviceType, IpConfig, IpRoute, + MacAddress, SSID, }, - types::{ConnectionFlags, Device, DeviceState, DeviceType, IpConfig, IpRoute, MacAddress}, }; use cidr::IpInet; use std::{collections::HashMap, net::IpAddr, str::FromStr}; @@ -44,6 +47,38 @@ pub struct DeviceFromProxyBuilder<'a> { proxy: &'a DeviceProxy<'a>, } +/// Builder to create an [AccessPoint] from its corresponding NetworkManager D-Bus representation. +pub struct AccessPointFromProxyBuilder<'a> { + device_name: String, + proxy: &'a AccessPointProxy<'a>, +} + +impl<'a> AccessPointFromProxyBuilder<'a> { + pub fn new(device_name: String, proxy: &'a AccessPointProxy<'a>) -> Self { + Self { device_name, proxy } + } + + /// Creates an [AccessPoint] starting on the [AccessPointProxy]. + pub async fn build(&self) -> Result { + let ssid = SSID(self.proxy.ssid().await?); + let hw_address = self.proxy.hw_address().await?; + let strength = self.proxy.strength().await?; + let flags = self.proxy.flags().await?; + let rsn_flags = self.proxy.rsn_flags().await?; + let wpa_flags = self.proxy.wpa_flags().await?; + + Ok(AccessPoint { + device: self.device_name.clone(), + ssid, + hw_address, + strength, + flags, + rsn_flags, + wpa_flags, + }) + } +} + impl<'a> ConnectionFromProxyBuilder<'a> { pub fn new(_connection: &zbus::Connection, proxy: &'a ConnectionProxy<'a>) -> Self { Self { proxy } diff --git a/rust/agama-network/src/nm/streams/common.rs b/rust/agama-network/src/nm/streams/common.rs index 3118a785b4..6d59bc1b8f 100644 --- a/rust/agama-network/src/nm/streams/common.rs +++ b/rust/agama-network/src/nm/streams/common.rs @@ -35,6 +35,8 @@ pub enum NmChange { ActiveConnectionAdded(OwnedObjectPath), ActiveConnectionUpdated(OwnedObjectPath), ActiveConnectionRemoved(OwnedObjectPath), + AccessPointAdded(OwnedObjectPath, OwnedObjectPath), + AccessPointRemoved(OwnedObjectPath, OwnedObjectPath), } pub async fn build_added_and_removed_stream( @@ -63,3 +65,17 @@ pub async fn build_properties_changed_stream( let stream = MessageStream::for_match_rule(rule, connection, Some(1)).await?; Ok(stream) } + +/// Returns a stream of wireless signals to be used by DeviceChangedStream. +/// +/// It listens for AccessPointAdded and AccessPointRemoved signals. +pub async fn build_wireless_signals_stream( + connection: &zbus::Connection, +) -> Result { + let rule = MatchRule::builder() + .msg_type(MessageType::Signal) + .interface("org.freedesktop.NetworkManager.Device.Wireless")? + .build(); + let stream = MessageStream::for_match_rule(rule, connection, Some(1)).await?; + Ok(stream) +} diff --git a/rust/agama-network/src/nm/streams/devices.rs b/rust/agama-network/src/nm/streams/devices.rs index fa6d71caec..e7c1009b33 100644 --- a/rust/agama-network/src/nm/streams/devices.rs +++ b/rust/agama-network/src/nm/streams/devices.rs @@ -33,7 +33,10 @@ use zbus::{ Message, MessageStream, }; -use super::common::{build_added_and_removed_stream, build_properties_changed_stream, NmChange}; +use super::common::{ + build_added_and_removed_stream, build_properties_changed_stream, build_wireless_signals_stream, + NmChange, +}; use crate::nm::error::NmError; /// Stream of device-related events. @@ -64,6 +67,10 @@ impl DeviceChangedStream { "properties", build_properties_changed_stream(&connection).await?, ); + inner.insert( + "wireless", + build_wireless_signals_stream(&connection).await?, + ); Ok(Self { connection, inner }) } @@ -161,6 +168,25 @@ impl DeviceChangedStream { return Self::handle_changed(changed); } + let header = message.header(); + let interface = header.interface()?; + if interface == "org.freedesktop.NetworkManager.Device.Wireless" { + let path = OwnedObjectPath::from(header.path()?.to_owned()); + let member = header.member()?; + + match member.as_str() { + "AccessPointAdded" => { + let ap_path: OwnedObjectPath = message.body().deserialize().ok()?; + return Some(NmChange::AccessPointAdded(path, ap_path)); + } + "AccessPointRemoved" => { + let ap_path: OwnedObjectPath = message.body().deserialize().ok()?; + return Some(NmChange::AccessPointRemoved(path, ap_path)); + } + _ => {} + } + } + None } } diff --git a/rust/agama-network/src/nm/watcher.rs b/rust/agama-network/src/nm/watcher.rs index 5467eb3e22..597367d7c9 100644 --- a/rust/agama-network/src/nm/watcher.rs +++ b/rust/agama-network/src/nm/watcher.rs @@ -24,10 +24,16 @@ //! the NetworkSystem state when devices or active connections change. use crate::model::Connection; +use crate::nm::proxies::SettingsProxy; use std::collections::{hash_map::Entry, HashMap}; +use std::str::FromStr; use crate::types::Device; -use crate::{adapter::Watcher, nm::proxies::DeviceProxy, Action, NetworkAdapterError}; +use crate::{ + adapter::Watcher, + nm::proxies::{AccessPointProxy, DeviceProxy}, + Action, NetworkAdapterError, +}; use anyhow::anyhow; use async_trait::async_trait; use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender}; @@ -35,7 +41,7 @@ use tokio_stream::StreamExt; use zbus::zvariant::OwnedObjectPath; use super::{ - builder::{ConnectionFromProxyBuilder, DeviceFromProxyBuilder}, + builder::{AccessPointFromProxyBuilder, ConnectionFromProxyBuilder, DeviceFromProxyBuilder}, client::NetworkManagerClient, dbus::connection_from_dbus, error::NmError, @@ -63,6 +69,8 @@ use super::{ /// * A device is added, changed or removed. /// * The status of a device changes. /// * The IPv4 or IPv6 configuration changes. +/// * A connection is added or removed. +/// * An access point is added or removed. pub struct NetworkManagerWatcher { connection: zbus::Connection, } @@ -183,6 +191,7 @@ impl ActionDispatcher<'_> { /// It runs until the updates channel is closed. pub async fn run(&mut self) -> Result<(), NmError> { self.read_devices().await?; + self.read_connections().await?; while let Some(update) = self.updates_rx.recv().await { let result = match update { NmChange::GeneralStateChanged => self.handle_general_state_changed().await, @@ -199,6 +208,12 @@ impl ActionDispatcher<'_> { NmChange::ActiveConnectionRemoved(path) => { self.handle_active_connection_removed(path).await } + NmChange::AccessPointAdded(device_path, ap_path) => { + self.handle_access_point_added(device_path, ap_path).await + } + NmChange::AccessPointRemoved(device_path, ap_path) => { + self.handle_access_point_removed(device_path, ap_path).await + } }; if let Err(error) = result { @@ -217,6 +232,18 @@ impl ActionDispatcher<'_> { Ok(()) } + /// Reads the connections. + async fn read_connections(&mut self) -> Result<(), NmError> { + let settings_proxy = SettingsProxy::new(&self.connection).await?; + for path in settings_proxy.list_connections().await? { + match self.proxies.find_or_add_connection(&path).await { + Ok((uuid, _)) => tracing::info!("Adding connection {}", &uuid), + Err(e) => tracing::info!("Cannot add connection {} because {:?}", &path, &e), + } + } + Ok(()) + } + /// Handles a general state change. async fn handle_general_state_changed(&mut self) -> Result<(), NmError> { tracing::info!("General state was changed"); @@ -251,8 +278,8 @@ impl ActionDispatcher<'_> { /// * `path`: D-Bus object path of the removed connection. async fn handle_connection_removed(&mut self, path: OwnedObjectPath) -> Result<(), NmError> { tracing::info!("Connection was removed"); - if let Some((id, _)) = self.proxies.remove_connection(&path) { - _ = self.actions_tx.send(Action::RemoveConnection(id)); + if let Some((uuid, _)) = self.proxies.remove_connection(&path) { + _ = self.actions_tx.send(Action::RemoveConnection(uuid)); } Ok(()) } @@ -328,12 +355,12 @@ impl ActionDispatcher<'_> { path: OwnedObjectPath, ) -> Result<(), NmError> { let proxy = self.proxies.find_or_add_active_connection(&path).await?; - let id = proxy.id().await?; + let uuid = Uuid::from_str(&proxy.uuid().await?).map_err(NmError::InvalidNetworkUUID)?; let state = proxy.state().await.map(NmConnectionState)?; if let Ok(state) = state.try_into() { _ = self .actions_tx - .send(Action::ChangeConnectionState(id, state)); + .send(Action::ChangeConnectionState(uuid, state)); } // TODO: report an error if the device cannot get generated @@ -348,18 +375,51 @@ impl ActionDispatcher<'_> { path: OwnedObjectPath, ) -> Result<(), NmError> { if let Some(proxy) = self.proxies.remove_active_connection(&path) { - let id = proxy.id().await?; + let uuid = Uuid::from_str(&proxy.uuid().await?).map_err(NmError::InvalidNetworkUUID)?; let state = proxy.state().await.map(NmConnectionState)?; if let Ok(state) = state.try_into() { _ = self .actions_tx - .send(Action::ChangeConnectionState(id, state)); + .send(Action::ChangeConnectionState(uuid, state)); } } Ok(()) } + /// Handles the case where a new access point appears. + /// + /// * `device_path`: D-Bus object path of the device. + /// * `ap_path`: D-Bus object path of the new access point. + async fn handle_access_point_added( + &mut self, + device_path: OwnedObjectPath, + ap_path: OwnedObjectPath, + ) -> Result<(), NmError> { + let (name, _) = self.proxies.find_or_add_device(&device_path).await?; + let device_name = name.clone(); + let (_, proxy) = self.proxies.find_or_add_access_point(&ap_path).await?; + if let Ok(ap) = Self::access_point_from_proxy(device_name, proxy.clone()).await { + _ = self.actions_tx.send(Action::AddAccessPoint(Box::new(ap))); + } + Ok(()) + } + + /// Handles the removal of an access point. + /// + /// * `device_path`: D-Bus object path of the device. + /// * `ap_path`: D-Bus object path of the removed access point. + async fn handle_access_point_removed( + &mut self, + _device_path: OwnedObjectPath, + ap_path: OwnedObjectPath, + ) -> Result<(), NmError> { + if let Some((hw_address, _)) = self.proxies.remove_access_point(&ap_path) { + _ = self.actions_tx.send(Action::RemoveAccessPoint(hw_address)); + } + Ok(()) + } + async fn connection_from_proxy( connection: &zbus::Connection, proxy: ConnectionProxy<'_>, @@ -375,15 +435,26 @@ impl ActionDispatcher<'_> { let builder = DeviceFromProxyBuilder::new(connection, &proxy); builder.build().await } + + async fn access_point_from_proxy( + device_name: String, + proxy: AccessPointProxy<'_>, + ) -> Result { + let builder = AccessPointFromProxyBuilder::new(device_name, &proxy); + builder.build().await + } } +use uuid::Uuid; + /// Ancillary class to track the devices and their related D-Bus objects. pub struct ProxiesRegistry<'a> { connection: zbus::Connection, - connections: HashMap)>, + connections: HashMap)>, // the String is the device name like eth0 devices: HashMap)>, active_connections: HashMap>, + access_points: HashMap)>, } impl<'a> ProxiesRegistry<'a> { @@ -393,6 +464,7 @@ impl<'a> ProxiesRegistry<'a> { connections: HashMap::new(), devices: HashMap::new(), active_connections: HashMap::new(), + access_points: HashMap::new(), } } @@ -424,7 +496,7 @@ impl<'a> ProxiesRegistry<'a> { pub async fn find_or_add_connection( &mut self, path: &OwnedObjectPath, - ) -> Result<&(String, ConnectionProxy<'a>), NmError> { + ) -> Result<&(Uuid, ConnectionProxy<'a>), NmError> { // Cannot use entry(...).or_insert_with(...) because of the async call. match self.connections.entry(path.clone()) { Entry::Vacant(entry) => { @@ -434,7 +506,7 @@ impl<'a> ProxiesRegistry<'a> { .await?; let settings = proxy.get_settings().await?; match connection_from_dbus(settings) { - Ok(conn) => Ok(entry.insert((conn.id, proxy))), + Ok(conn) => Ok(entry.insert((conn.uuid, proxy))), Err(e) => { tracing::warn!("Could not process connection {}: {}", &path, e); Err(e) @@ -466,13 +538,35 @@ impl<'a> ProxiesRegistry<'a> { } } + /// Finds or adds an access point to the registry. + /// + /// * `path`: D-Bus object path. + pub async fn find_or_add_access_point( + &mut self, + path: &OwnedObjectPath, + ) -> Result<&(String, AccessPointProxy<'a>), NmError> { + // Cannot use entry(...).or_insert_with(...) because of the async call. + match self.access_points.entry(path.clone()) { + Entry::Vacant(entry) => { + let proxy = AccessPointProxy::builder(&self.connection.clone()) + .path(path.clone())? + .build() + .await?; + let hw_address = proxy.hw_address().await?; + + Ok(entry.insert((hw_address, proxy))) + } + Entry::Occupied(entry) => Ok(entry.into_mut()), + } + } + /// Removes a connection from the registry. /// /// * `path`: D-Bus object path. pub fn remove_connection( &mut self, path: &OwnedObjectPath, - ) -> Option<(String, ConnectionProxy<'_>)> { + ) -> Option<(Uuid, ConnectionProxy<'_>)> { self.connections.remove(path) } @@ -493,6 +587,16 @@ impl<'a> ProxiesRegistry<'a> { self.devices.remove(path) } + /// Removes an access point from the registry. + /// + /// * `path`: D-Bus object path. + pub fn remove_access_point( + &mut self, + path: &OwnedObjectPath, + ) -> Option<(String, AccessPointProxy<'_>)> { + self.access_points.remove(path) + } + //// Updates a device name. /// /// * `path`: D-Bus object path. diff --git a/rust/agama-network/src/service.rs b/rust/agama-network/src/service.rs index 37628faa64..cf79e6fbf4 100644 --- a/rust/agama-network/src/service.rs +++ b/rust/agama-network/src/service.rs @@ -390,14 +390,38 @@ impl Service { Action::GetDevices(tx) => { tx.send(self.state.devices.clone()).unwrap(); } + Action::AddAccessPoint(ap) => { + self.state.add_access_point(*ap.clone())?; + tracing::info!("Access point added: {:?}", &ap); + //self.events.send(Event::SystemChanged { + // scope: (Scope::Network), + //})?; + return Ok(Some(NetworkChange::AccessPointAdded(*ap))); + } + Action::RemoveAccessPoint(hw_address) => { + self.state.remove_access_point(&hw_address)?; + tracing::info!("Access point removed: {:?}", &hw_address); + //self.events.send(Event::SystemChanged { + // scope: (Scope::Network), + //})?; + return Ok(Some(NetworkChange::AccessPointRemoved(hw_address))); + } Action::UpdateConnection(conn, tx) => { let result = self.state.update_connection(*conn); tx.send(result).unwrap(); } - Action::ChangeConnectionState(id, state) => { - if let Some(conn) = self.state.get_connection_mut(&id) { - conn.state = state; - return Ok(Some(NetworkChange::ConnectionStateChanged { id, state })); + Action::ChangeConnectionState(uuid, state) => { + if let Some(conn) = self.state.get_connection_by_uuid_mut(uuid) { + if conn.state != state { + tracing::info!( + "Changed connection {} state: ({} -> {})", + conn.id, + conn.state, + state + ); + conn.state = state; + } + return Ok(Some(NetworkChange::ConnectionStateChanged { uuid, state })); } } Action::UpdateGeneralState(general_state) => { @@ -411,11 +435,11 @@ impl Service { })?; } } - Action::RemoveConnection(id) => { - if let Some(conn) = self.state.get_connection(id.as_ref()) { + Action::RemoveConnection(uuid) => { + if let Some(conn) = self.state.get_connection_by_uuid(uuid) { if !conn.is_removed() { tracing::info!("Connection {:?} exists, removing it", conn); - self.state.remove_connection(id.as_str())?; + self.state.remove_connection(uuid)?; self.events.send(Event::ConfigChanged { scope: (Scope::Network), })?; @@ -426,9 +450,9 @@ impl Service { ); } } else { - tracing::info!("Connection {} does not exists, skipping it", id); + tracing::info!("Connection {} does not exists, skipping it", uuid); } - return Ok(Some(NetworkChange::ConnectionRemoved(id))); + return Ok(Some(NetworkChange::ConnectionRemoved(uuid))); } Action::Apply(tx) => { let result = self.apply().await; @@ -459,7 +483,10 @@ impl Service { Ok(state) => { if self.state != state { self.state = state; - self.events.send(Event::ConfigChanged { + self.events.send(Event::ProposalChanged { + scope: (Scope::Network), + })?; + self.events.send(Event::SystemChanged { scope: (Scope::Network), })?; } diff --git a/rust/agama-utils/src/api/network/settings.rs b/rust/agama-utils/src/api/network/settings.rs index a7a0645f21..2dbe9b84cb 100644 --- a/rust/agama-utils/src/api/network/settings.rs +++ b/rust/agama-utils/src/api/network/settings.rs @@ -354,4 +354,3 @@ mod tests { assert_eq!(conn.dns_searchlist, vec!["example.org"]); } } -