diff --git a/rust/Cargo.lock b/rust/Cargo.lock index f9481c5eb3..a1a1a8c5d2 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -47,6 +47,7 @@ dependencies = [ "agama-lib", "agama-locale-data", "anyhow", + "async-trait", "cidr", "gettext-rs", "log", @@ -328,9 +329,9 @@ checksum = "b4eb2cdb97421e01129ccb49169d8279ed21e829929144f4a22a6e54ac549ca1" [[package]] name = "async-trait" -version = "0.1.74" +version = "0.1.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" +checksum = "fdf6721fb0140e4f897002dd086c06f6c27775df19cfe1fccb21181a48fd2c98" dependencies = [ "proc-macro2", "quote", diff --git a/rust/agama-dbus-server/Cargo.toml b/rust/agama-dbus-server/Cargo.toml index e6e95c67a0..4079ee01ca 100644 --- a/rust/agama-dbus-server/Cargo.toml +++ b/rust/agama-dbus-server/Cargo.toml @@ -25,3 +25,4 @@ gettext-rs = { version = "0.7.0", features = ["gettext-system"] } regex = "1.10.2" once_cell = "1.18.0" macaddr = "1.0" +async-trait = "0.1.75" diff --git a/rust/agama-dbus-server/src/network/action.rs b/rust/agama-dbus-server/src/network/action.rs index 3ddf6687d9..568410d579 100644 --- a/rust/agama-dbus-server/src/network/action.rs +++ b/rust/agama-dbus-server/src/network/action.rs @@ -2,6 +2,7 @@ use crate::network::model::Connection; use agama_lib::network::types::DeviceType; use tokio::sync::oneshot; use uuid::Uuid; +use zbus::zvariant::OwnedObjectPath; use super::error::NetworkStateError; @@ -15,7 +16,11 @@ pub type ControllerConnection = (Connection, Vec); #[derive(Debug)] pub enum Action { /// Add a new connection with the given name and type. - AddConnection(String, DeviceType), + AddConnection( + String, + DeviceType, + Responder>, + ), /// Gets a connection GetConnection(Uuid, Responder>), /// Gets a controller connection @@ -25,9 +30,13 @@ pub enum Action { ), /// Sets a controller's ports. It uses the Uuid of the controller and the IDs or interface names /// of the ports. - SetPorts(Uuid, Vec, Responder>), + SetPorts( + Uuid, + Box>, + Responder>, + ), /// Update a connection (replacing the old one). - UpdateConnection(Connection), + UpdateConnection(Box), /// Remove the connection with the given Uuid. RemoveConnection(String), /// Apply the current configuration. diff --git a/rust/agama-dbus-server/src/network/dbus/interfaces.rs b/rust/agama-dbus-server/src/network/dbus/interfaces.rs index 3cd5fb8e3d..be287a5776 100644 --- a/rust/agama-dbus-server/src/network/dbus/interfaces.rs +++ b/rust/agama-dbus-server/src/network/dbus/interfaces.rs @@ -3,614 +3,12 @@ //! This module contains the set of D-Bus interfaces that are exposed by [D-Bus network //! service](crate::NetworkService). -use super::ObjectsRegistry; -use crate::network::{ - action::Action, - error::NetworkStateError, - model::{ - BondConfig, Connection as NetworkConnection, ConnectionConfig, Device as NetworkDevice, - MacAddress, SecurityProtocol, WirelessConfig, WirelessMode, - }, -}; - -use agama_lib::network::types::SSID; -use std::{str::FromStr, sync::Arc}; -use tokio::sync::{mpsc::UnboundedSender, oneshot}; -use tokio::sync::{MappedMutexGuard, Mutex, MutexGuard}; -use uuid::Uuid; -use zbus::{ - dbus_interface, - zvariant::{ObjectPath, OwnedObjectPath}, - SignalContext, -}; - +mod common; +mod connection_configs; +mod connections; +mod devices; mod ip_config; +pub use connection_configs::{Bond, Wireless}; +pub use connections::{Connection, Connections, Match}; +pub use devices::{Device, Devices}; pub use ip_config::Ip; - -/// D-Bus interface for the network devices collection -/// -/// It offers an API to query the devices collection. -pub struct Devices { - objects: Arc>, -} - -impl Devices { - /// Creates a Devices interface object. - /// - /// * `objects`: Objects paths registry. - pub fn new(objects: Arc>) -> Self { - Self { objects } - } -} - -#[dbus_interface(name = "org.opensuse.Agama1.Network.Devices")] -impl Devices { - /// Returns the D-Bus paths of the network devices. - pub async fn get_devices(&self) -> Vec { - let objects = self.objects.lock().await; - objects - .devices_paths() - .iter() - .filter_map(|c| ObjectPath::try_from(c.clone()).ok()) - .collect() - } -} - -/// D-Bus interface for a network device -/// -/// It offers an API to query basic networking devices information (e.g., the name). -pub struct Device { - device: NetworkDevice, -} - -impl Device { - /// Creates an interface object. - /// - /// * `device`: network device. - pub fn new(device: NetworkDevice) -> Self { - Self { device } - } -} - -#[dbus_interface(name = "org.opensuse.Agama1.Network.Device")] -impl Device { - /// Device name. - /// - /// Kernel device name, e.g., eth0, enp1s0, etc. - #[dbus_interface(property)] - pub fn name(&self) -> &str { - &self.device.name - } - - /// Device type. - /// - /// Possible values: 0 = loopback, 1 = ethernet, 2 = wireless. - /// - /// See [agama_lib::network::types::DeviceType]. - #[dbus_interface(property, name = "Type")] - pub fn device_type(&self) -> u8 { - self.device.type_ as u8 - } -} - -/// D-Bus interface for the set of connections. -/// -/// It offers an API to query the connections collection. -pub struct Connections { - actions: Arc>>, - objects: Arc>, -} - -impl Connections { - /// Creates a Connections interface object. - /// - /// * `objects`: Objects paths registry. - pub fn new(objects: Arc>, actions: UnboundedSender) -> Self { - Self { - objects, - actions: Arc::new(Mutex::new(actions)), - } - } -} - -#[dbus_interface(name = "org.opensuse.Agama1.Network.Connections")] -impl Connections { - /// Returns the D-Bus paths of the network connections. - pub async fn get_connections(&self) -> Vec { - let objects = self.objects.lock().await; - objects - .connections_paths() - .iter() - .filter_map(|c| ObjectPath::try_from(c.clone()).ok()) - .collect() - } - - /// Adds a new network connection. - /// - /// * `id`: connection name. - /// * `ty`: connection type (see [agama_lib::network::types::DeviceType]). - pub async fn add_connection(&mut self, id: String, ty: u8) -> zbus::fdo::Result<()> { - let actions = self.actions.lock().await; - actions - .send(Action::AddConnection(id.clone(), ty.try_into()?)) - .unwrap(); - Ok(()) - } - - /// Returns the D-Bus path of the network connection. - /// - /// * `id`: connection ID. - pub async fn get_connection(&self, id: &str) -> zbus::fdo::Result { - let objects = self.objects.lock().await; - match objects.connection_path(id) { - Some(path) => Ok(path.into()), - None => Err(NetworkStateError::UnknownConnection(id.to_string()).into()), - } - } - - /// Removes a network connection. - /// - /// * `uuid`: connection UUID.. - pub async fn remove_connection(&mut self, id: &str) -> zbus::fdo::Result<()> { - let actions = self.actions.lock().await; - actions - .send(Action::RemoveConnection(id.to_string())) - .unwrap(); - Ok(()) - } - - /// Applies the network configuration. - /// - /// It includes adding, updating and removing connections as needed. - pub async fn apply(&self) -> zbus::fdo::Result<()> { - let actions = self.actions.lock().await; - actions.send(Action::Apply).unwrap(); - Ok(()) - } - - /// Notifies than a new interface has been added. - #[dbus_interface(signal)] - pub async fn connection_added( - ctxt: &SignalContext<'_>, - id: &str, - path: &ObjectPath<'_>, - ) -> zbus::Result<()>; -} - -/// D-Bus interface for a network connection -/// -/// It offers an API to query a connection. -pub struct Connection { - actions: Arc>>, - connection: Arc>, -} - -impl Connection { - /// Creates a Connection interface object. - /// - /// * `actions`: sending-half of a channel to send actions. - /// * `connection`: connection to expose over D-Bus. - pub fn new( - actions: UnboundedSender, - connection: Arc>, - ) -> Self { - Self { - actions: Arc::new(Mutex::new(actions)), - connection, - } - } - - /// Returns the underlying connection. - async fn get_connection(&self) -> MutexGuard { - self.connection.lock().await - } - - /// Updates the connection data in the NetworkSystem. - /// - /// * `connection`: Updated connection. - async fn update_connection<'a>( - &self, - connection: MutexGuard<'a, NetworkConnection>, - ) -> zbus::fdo::Result<()> { - let actions = self.actions.lock().await; - actions - .send(Action::UpdateConnection(connection.clone())) - .unwrap(); - Ok(()) - } -} - -#[dbus_interface(name = "org.opensuse.Agama1.Network.Connection")] -impl Connection { - /// Connection ID. - /// - /// Unique identifier of the network connection. It may or not be the same that the used by the - /// backend. For instance, when using NetworkManager (which is the only supported backend by - /// now), it uses the original ID but appending a number in case the ID is duplicated. - #[dbus_interface(property)] - pub async fn id(&self) -> String { - self.get_connection().await.id.to_string() - } - - /// Connection UUID. - /// - /// Unique identifier of the network connection. It may or not be the same that the used by the - /// backend. - #[dbus_interface(property)] - pub async fn uuid(&self) -> String { - self.get_connection().await.uuid.to_string() - } - - #[dbus_interface(property)] - pub async fn controller(&self) -> String { - let connection = self.get_connection().await; - match connection.controller { - Some(uuid) => uuid.to_string(), - None => "".to_string(), - } - } - - #[dbus_interface(property)] - pub async fn interface(&self) -> String { - let connection = self.get_connection().await; - connection.interface.as_deref().unwrap_or("").to_string() - } - - #[dbus_interface(property)] - pub async fn set_interface(&mut self, name: &str) -> zbus::fdo::Result<()> { - let mut connection = self.get_connection().await; - connection.interface = Some(name.to_string()); - self.update_connection(connection).await - } - - /// Custom mac-address - #[dbus_interface(property)] - pub async fn mac_address(&self) -> String { - self.get_connection().await.mac_address.to_string() - } - - #[dbus_interface(property)] - pub async fn set_mac_address(&mut self, mac_address: &str) -> zbus::fdo::Result<()> { - let mut connection = self.get_connection().await; - connection.mac_address = MacAddress::from_str(mac_address)?; - self.update_connection(connection).await - } -} - -/// D-Bus interface for Match settings -pub struct Match { - actions: Arc>>, - connection: Arc>, -} - -impl Match { - /// Creates a Match Settings interface object. - /// - /// * `actions`: sending-half of a channel to send actions. - /// * `connection`: connection to expose over D-Bus. - pub fn new( - actions: UnboundedSender, - connection: Arc>, - ) -> Self { - Self { - actions: Arc::new(Mutex::new(actions)), - connection, - } - } - - /// Returns the underlying connection. - async fn get_connection(&self) -> MutexGuard { - self.connection.lock().await - } - - /// Updates the connection data in the NetworkSystem. - /// - /// * `connection`: Updated connection. - async fn update_connection<'a>( - &self, - connection: MutexGuard<'a, NetworkConnection>, - ) -> zbus::fdo::Result<()> { - let actions = self.actions.lock().await; - actions - .send(Action::UpdateConnection(connection.clone())) - .unwrap(); - Ok(()) - } -} - -/// D-Bus interface for Bond settings. -pub struct Bond { - actions: Arc>>, - uuid: Uuid, -} - -impl Bond { - /// Creates a Bond interface object. - /// - /// * `actions`: sending-half of a channel to send actions. - /// * `uuid`: connection UUID. - pub fn new(actions: UnboundedSender, uuid: Uuid) -> Self { - Self { - actions: Arc::new(Mutex::new(actions)), - uuid, - } - } - - /// Gets the connection. - async fn get_connection(&self) -> Result { - let actions = self.actions.lock().await; - let (tx, rx) = oneshot::channel(); - actions.send(Action::GetConnection(self.uuid, tx)).unwrap(); - rx.await - .unwrap() - .ok_or(NetworkStateError::UnknownConnection(self.uuid.to_string())) - } - - /// Gets the bonding configuration. - pub async fn get_config(&self) -> Result { - let connection = self.get_connection().await?; - match connection.config { - ConnectionConfig::Bond(bond) => Ok(bond), - _ => panic!("Not a bond connection. This is most probably a bug."), - } - } - - /// Updates the bond configuration. - /// - /// * `func`: function to update the configuration. - pub async fn update_config(&self, func: F) -> Result<(), NetworkStateError> - where - F: FnOnce(&mut BondConfig), - { - let mut connection = self.get_connection().await?; - match &mut connection.config { - ConnectionConfig::Bond(bond) => func(bond), - _ => panic!("Not a bond connection. This is most probably a bug."), - } - let actions = self.actions.lock().await; - actions - .send(Action::UpdateConnection(connection.clone())) - .unwrap(); - Ok(()) - } -} - -#[dbus_interface(name = "org.opensuse.Agama1.Network.Connection.Bond")] -impl Bond { - /// Bonding mode. - #[dbus_interface(property)] - pub async fn mode(&self) -> zbus::fdo::Result { - let config = self.get_config().await?; - Ok(config.mode.to_string()) - } - - #[dbus_interface(property)] - pub async fn set_mode(&mut self, mode: &str) -> zbus::fdo::Result<()> { - let mode = mode.try_into()?; - self.update_config(|c| c.mode = mode).await?; - Ok(()) - } - - /// List of bonding options. - #[dbus_interface(property)] - pub async fn options(&self) -> zbus::fdo::Result { - let config = self.get_config().await?; - Ok(config.options.to_string()) - } - - #[dbus_interface(property)] - pub async fn set_options(&mut self, opts: &str) -> zbus::fdo::Result<()> { - let opts = opts.try_into()?; - self.update_config(|c| c.options = opts).await?; - Ok(()) - } - - /// List of bond ports. - /// - /// For the port names, it uses the interface name (preferred) or, as a fallback, - /// the connection ID of the port. - #[dbus_interface(property)] - pub async fn ports(&self) -> zbus::fdo::Result> { - let actions = self.actions.lock().await; - let (tx, rx) = oneshot::channel(); - actions.send(Action::GetController(self.uuid, tx)).unwrap(); - - let (_, ports) = rx.await.unwrap()?; - Ok(ports) - } - - #[dbus_interface(property)] - pub async fn set_ports(&mut self, ports: Vec) -> zbus::fdo::Result<()> { - let actions = self.actions.lock().await; - let (tx, rx) = oneshot::channel(); - actions - .send(Action::SetPorts(self.uuid, ports, tx)) - .unwrap(); - let result = rx.await.unwrap(); - Ok(result?) - } -} - -#[dbus_interface(name = "org.opensuse.Agama1.Network.Connection.Match")] -impl Match { - /// List of driver names to match. - #[dbus_interface(property)] - pub async fn driver(&self) -> Vec { - let connection = self.get_connection().await; - connection.match_config.driver.clone() - } - - #[dbus_interface(property)] - pub async fn set_driver(&mut self, driver: Vec) -> zbus::fdo::Result<()> { - let mut connection = self.get_connection().await; - connection.match_config.driver = driver; - self.update_connection(connection).await - } - - /// List of paths to match agains the ID_PATH udev property of devices. - #[dbus_interface(property)] - pub async fn path(&self) -> Vec { - let connection = self.get_connection().await; - connection.match_config.path.clone() - } - - #[dbus_interface(property)] - pub async fn set_path(&mut self, path: Vec) -> zbus::fdo::Result<()> { - let mut connection = self.get_connection().await; - connection.match_config.path = path; - self.update_connection(connection).await - } - /// List of interface names to match. - #[dbus_interface(property)] - pub async fn interface(&self) -> Vec { - let connection = self.get_connection().await; - connection.match_config.interface.clone() - } - - #[dbus_interface(property)] - pub async fn set_interface(&mut self, interface: Vec) -> zbus::fdo::Result<()> { - let mut connection = self.get_connection().await; - connection.match_config.interface = interface; - self.update_connection(connection).await - } - - /// List of kernel options to match. - #[dbus_interface(property)] - pub async fn kernel(&self) -> Vec { - let connection = self.get_connection().await; - connection.match_config.kernel.clone() - } - - #[dbus_interface(property)] - pub async fn set_kernel(&mut self, kernel: Vec) -> zbus::fdo::Result<()> { - let mut connection = self.get_connection().await; - connection.match_config.kernel = kernel; - self.update_connection(connection).await - } -} - -/// D-Bus interface for wireless settings -pub struct Wireless { - actions: Arc>>, - connection: Arc>, -} - -impl Wireless { - /// Creates a Wireless interface object. - /// - /// * `actions`: sending-half of a channel to send actions. - /// * `connection`: connection to expose over D-Bus. - pub fn new( - actions: UnboundedSender, - connection: Arc>, - ) -> Self { - Self { - actions: Arc::new(Mutex::new(actions)), - connection, - } - } - - /// Gets the wireless connection. - /// - /// Beware that it crashes when it is not a wireless connection. - async fn get_wireless(&self) -> MappedMutexGuard { - MutexGuard::map(self.connection.lock().await, |c| match &mut c.config { - ConnectionConfig::Wireless(config) => config, - _ => panic!("Not a wireless connection. This is most probably a bug."), - }) - } - - /// Updates the wireless configuration. - /// - /// * `func`: function to update the configuration. - pub async fn update_config(&self, func: F) -> Result<(), NetworkStateError> - where - F: FnOnce(&mut WirelessConfig), - { - let mut connection = self.connection.lock().await; - match &mut connection.config { - ConnectionConfig::Wireless(wireless) => func(wireless), - _ => panic!("Not a wireless connection. This is most probably a bug."), - } - let actions = self.actions.lock().await; - actions - .send(Action::UpdateConnection(connection.clone())) - .unwrap(); - Ok(()) - } -} - -#[dbus_interface(name = "org.opensuse.Agama1.Network.Connection.Wireless")] -impl Wireless { - /// Network SSID. - #[dbus_interface(property, name = "SSID")] - pub async fn ssid(&self) -> Vec { - let wireless = self.get_wireless().await; - wireless.ssid.clone().into() - } - - #[dbus_interface(property, name = "SSID")] - pub async fn set_ssid(&mut self, ssid: Vec) -> zbus::fdo::Result<()> { - self.update_config(|c| c.ssid = SSID(ssid)).await?; - Ok(()) - } - - /// Wireless connection mode. - /// - /// Possible values: "unknown", "adhoc", "infrastructure", "ap" or "mesh". - /// - /// See [crate::network::model::WirelessMode]. - #[dbus_interface(property)] - pub async fn mode(&self) -> String { - let wireless = self.get_wireless().await; - wireless.mode.to_string() - } - - #[dbus_interface(property)] - pub async fn set_mode(&mut self, mode: &str) -> zbus::fdo::Result<()> { - let mode: WirelessMode = mode.try_into()?; - self.update_config(|c| c.mode = mode).await?; - Ok(()) - } - - /// Password to connect to the wireless network. - #[dbus_interface(property)] - pub async fn password(&self) -> String { - let wireless = self.get_wireless().await; - wireless.password.clone().unwrap_or("".to_string()) - } - - #[dbus_interface(property)] - pub async fn set_password(&mut self, password: String) -> zbus::fdo::Result<()> { - self.update_config(|c| { - c.password = if password.is_empty() { - None - } else { - Some(password) - }; - }) - .await?; - Ok(()) - } - - /// Wireless security protocol. - /// - /// Possible values: "none", "owe", "ieee8021x", "wpa-psk", "sae", "wpa-eap", - /// "wpa-eap-suite-b192". - /// - /// See [crate::network::model::SecurityProtocol]. - #[dbus_interface(property)] - pub async fn security(&self) -> String { - let wireless = self.get_wireless().await; - wireless.security.to_string() - } - - #[dbus_interface(property)] - pub async fn set_security(&mut self, security: &str) -> zbus::fdo::Result<()> { - let security: SecurityProtocol = security - .try_into() - .map_err(|_| NetworkStateError::InvalidSecurityProtocol(security.to_string()))?; - self.update_config(|c| c.security = security).await?; - Ok(()) - } -} diff --git a/rust/agama-dbus-server/src/network/dbus/interfaces/common.rs b/rust/agama-dbus-server/src/network/dbus/interfaces/common.rs new file mode 100644 index 0000000000..e5a49f122c --- /dev/null +++ b/rust/agama-dbus-server/src/network/dbus/interfaces/common.rs @@ -0,0 +1,81 @@ +//! Traits to build network D-Bus interfaces. +//! +//! There are a set of operations that are shared by many D-Bus interfaces (retrieving or updating a connection, a configuration etc.). +//! The traits in this module implements the common pieces to make it easier to build new +//! interfaces and reduce code duplication. +//! +//! Note: it is not clear to us whether using traits or simple structs is better for this use case. +//! We could change the approach in the future. +use crate::network::{ + error::NetworkStateError, + model::{Connection as NetworkConnection, ConnectionConfig}, + Action, +}; +use async_trait::async_trait; +use tokio::sync::{mpsc::UnboundedSender, oneshot, MutexGuard}; +use uuid::Uuid; + +#[async_trait] +pub trait ConnectionInterface { + fn uuid(&self) -> Uuid; + + async fn actions(&self) -> MutexGuard>; + + async fn get_connection(&self) -> Result { + let actions = self.actions().await; + let (tx, rx) = oneshot::channel(); + actions + .send(Action::GetConnection(self.uuid(), tx)) + .unwrap(); + rx.await + .unwrap() + .ok_or(NetworkStateError::UnknownConnection( + self.uuid().to_string(), + )) + } + + /// Updates the connection data in the NetworkSystem. + /// + /// * `func`: function to update the connection. + async fn update_connection(&self, func: F) -> Result<(), NetworkStateError> + where + F: FnOnce(&mut NetworkConnection) + std::marker::Send, + { + let mut connection = self.get_connection().await?; + func(&mut connection); + let actions = self.actions().await; + actions + .send(Action::UpdateConnection(Box::new(connection))) + .unwrap(); + Ok(()) + } +} + +#[async_trait] +pub trait ConnectionConfigInterface: ConnectionInterface { + async fn get_config(&self) -> Result + where + T: TryFrom, + { + let connection = self.get_connection().await?; + connection.config.try_into() + } + + async fn update_config(&self, func: F) -> Result<(), NetworkStateError> + where + F: FnOnce(&mut T) + std::marker::Send, + T: Into + + TryFrom + + std::marker::Send, + { + let mut connection = self.get_connection().await?; + let mut config: T = connection.config.clone().try_into()?; + func(&mut config); + connection.config = config.into(); + let actions = self.actions().await; + actions + .send(Action::UpdateConnection(Box::new(connection))) + .unwrap(); + Ok(()) + } +} diff --git a/rust/agama-dbus-server/src/network/dbus/interfaces/connection_configs.rs b/rust/agama-dbus-server/src/network/dbus/interfaces/connection_configs.rs new file mode 100644 index 0000000000..c0967bb93e --- /dev/null +++ b/rust/agama-dbus-server/src/network/dbus/interfaces/connection_configs.rs @@ -0,0 +1,214 @@ +use agama_lib::network::types::SSID; +use async_trait::async_trait; +use std::sync::Arc; +use tokio::sync::{mpsc::UnboundedSender, oneshot, Mutex, MutexGuard}; +use uuid::Uuid; +use zbus::dbus_interface; + +use crate::network::{ + action::Action, + error::NetworkStateError, + model::{BondConfig, SecurityProtocol, WirelessConfig, WirelessMode}, +}; + +use super::common::{ConnectionConfigInterface, ConnectionInterface}; + +/// D-Bus interface for Bond settings. +pub struct Bond { + actions: Arc>>, + uuid: Uuid, +} + +impl Bond { + /// Creates a Bond interface object. + /// + /// * `actions`: sending-half of a channel to send actions. + /// * `uuid`: connection UUID. + pub fn new(actions: UnboundedSender, uuid: Uuid) -> Self { + Self { + actions: Arc::new(Mutex::new(actions)), + uuid, + } + } +} + +#[dbus_interface(name = "org.opensuse.Agama1.Network.Connection.Bond")] +impl Bond { + /// Bonding mode. + #[dbus_interface(property)] + pub async fn mode(&self) -> zbus::fdo::Result { + let config = self.get_config::().await?; + Ok(config.mode.to_string()) + } + + #[dbus_interface(property)] + pub async fn set_mode(&mut self, mode: &str) -> zbus::fdo::Result<()> { + let mode = mode.try_into()?; + self.update_config::(|c| c.mode = mode) + .await?; + Ok(()) + } + + /// List of bonding options. + #[dbus_interface(property)] + pub async fn options(&self) -> zbus::fdo::Result { + let config = self.get_config::().await?; + Ok(config.options.to_string()) + } + + #[dbus_interface(property)] + pub async fn set_options(&mut self, opts: &str) -> zbus::fdo::Result<()> { + let opts = opts.try_into()?; + self.update_config::(|c| c.options = opts) + .await?; + Ok(()) + } + + /// List of bond ports. + /// + /// For the port names, it uses the interface name (preferred) or, as a fallback, + /// the connection ID of the port. + #[dbus_interface(property)] + pub async fn ports(&self) -> zbus::fdo::Result> { + let actions = self.actions.lock().await; + let (tx, rx) = oneshot::channel(); + actions.send(Action::GetController(self.uuid, tx)).unwrap(); + + let (_, ports) = rx.await.unwrap()?; + Ok(ports) + } + + #[dbus_interface(property)] + pub async fn set_ports(&mut self, ports: Vec) -> zbus::fdo::Result<()> { + let actions = self.actions.lock().await; + let (tx, rx) = oneshot::channel(); + actions + .send(Action::SetPorts(self.uuid, Box::new(ports), tx)) + .unwrap(); + let result = rx.await.unwrap(); + Ok(result?) + } +} + +#[async_trait] +impl ConnectionInterface for Bond { + fn uuid(&self) -> Uuid { + self.uuid + } + + async fn actions(&self) -> MutexGuard> { + self.actions.lock().await + } +} + +impl ConnectionConfigInterface for Bond {} + +/// D-Bus interface for wireless settings +pub struct Wireless { + actions: Arc>>, + uuid: Uuid, +} + +impl Wireless { + /// Creates a Wireless interface object. + /// + /// * `actions`: sending-half of a channel to send actions. + /// * `uuid`: connection UUID. + pub fn new(actions: UnboundedSender, uuid: Uuid) -> Self { + Self { + actions: Arc::new(Mutex::new(actions)), + uuid, + } + } +} + +#[dbus_interface(name = "org.opensuse.Agama1.Network.Connection.Wireless")] +impl Wireless { + /// Network SSID. + #[dbus_interface(property, name = "SSID")] + pub async fn ssid(&self) -> zbus::fdo::Result> { + let config = self.get_config::().await?; + Ok(config.ssid.into()) + } + + #[dbus_interface(property, name = "SSID")] + pub async fn set_ssid(&mut self, ssid: Vec) -> zbus::fdo::Result<()> { + self.update_config::(|c| c.ssid = SSID(ssid)) + .await?; + Ok(()) + } + + /// Wireless connection mode. + /// + /// Possible values: "unknown", "adhoc", "infrastructure", "ap" or "mesh". + /// + /// See [crate::network::model::WirelessMode]. + #[dbus_interface(property)] + pub async fn mode(&self) -> zbus::fdo::Result { + let config = self.get_config::().await?; + Ok(config.mode.to_string()) + } + + #[dbus_interface(property)] + pub async fn set_mode(&mut self, mode: &str) -> zbus::fdo::Result<()> { + let mode: WirelessMode = mode.try_into()?; + self.update_config::(|c| c.mode = mode) + .await?; + Ok(()) + } + + /// Password to connect to the wireless network. + #[dbus_interface(property)] + pub async fn password(&self) -> zbus::fdo::Result { + let config = self.get_config::().await?; + Ok(config.password.unwrap_or_default()) + } + + #[dbus_interface(property)] + pub async fn set_password(&mut self, password: String) -> zbus::fdo::Result<()> { + self.update_config::(|c| { + c.password = if password.is_empty() { + None + } else { + Some(password) + }; + }) + .await?; + Ok(()) + } + + /// Wireless security protocol. + /// + /// Possible values: "none", "owe", "ieee8021x", "wpa-psk", "sae", "wpa-eap", + /// "wpa-eap-suite-b192". + /// + /// See [crate::network::model::SecurityProtocol]. + #[dbus_interface(property)] + pub async fn security(&self) -> zbus::fdo::Result { + let config = self.get_config::().await?; + Ok(config.security.to_string()) + } + + #[dbus_interface(property)] + pub async fn set_security(&mut self, security: &str) -> zbus::fdo::Result<()> { + let security: SecurityProtocol = security + .try_into() + .map_err(|_| NetworkStateError::InvalidSecurityProtocol(security.to_string()))?; + self.update_config::(|c| c.security = security) + .await?; + Ok(()) + } +} + +#[async_trait] +impl ConnectionInterface for Wireless { + fn uuid(&self) -> Uuid { + self.uuid + } + + async fn actions(&self) -> MutexGuard> { + self.actions.lock().await + } +} + +impl ConnectionConfigInterface for Wireless {} diff --git a/rust/agama-dbus-server/src/network/dbus/interfaces/connections.rs b/rust/agama-dbus-server/src/network/dbus/interfaces/connections.rs new file mode 100644 index 0000000000..e8df645ec5 --- /dev/null +++ b/rust/agama-dbus-server/src/network/dbus/interfaces/connections.rs @@ -0,0 +1,305 @@ +use async_trait::async_trait; +use std::{str::FromStr, sync::Arc}; +use tokio::sync::{mpsc::UnboundedSender, oneshot, Mutex, MutexGuard}; +use uuid::Uuid; +use zbus::{ + dbus_interface, + zvariant::{ObjectPath, OwnedObjectPath}, + SignalContext, +}; + +use super::common::ConnectionInterface; +use crate::network::{dbus::ObjectsRegistry, error::NetworkStateError, model::MacAddress, Action}; + +/// D-Bus interface for the set of connections. +/// +/// It offers an API to query the connections collection. +pub struct Connections { + actions: Arc>>, + objects: Arc>, +} + +impl Connections { + /// Creates a Connections interface object. + /// + /// * `objects`: Objects paths registry. + pub fn new(objects: Arc>, actions: UnboundedSender) -> Self { + Self { + objects, + actions: Arc::new(Mutex::new(actions)), + } + } +} + +#[dbus_interface(name = "org.opensuse.Agama1.Network.Connections")] +impl Connections { + /// Returns the D-Bus paths of the network connections. + pub async fn get_connections(&self) -> Vec { + let objects = self.objects.lock().await; + objects + .connections_paths() + .iter() + .filter_map(|c| ObjectPath::try_from(c.clone()).ok()) + .collect() + } + + /// Adds a new network connection. + /// + /// * `id`: connection name. + /// * `ty`: connection type (see [agama_lib::network::types::DeviceType]). + pub async fn add_connection( + &mut self, + id: String, + ty: u8, + #[zbus(signal_context)] ctxt: SignalContext<'_>, + ) -> zbus::fdo::Result { + let actions = self.actions.lock().await; + let (tx, rx) = oneshot::channel(); + actions + .send(Action::AddConnection(id.clone(), ty.try_into()?, tx)) + .unwrap(); + let path = rx.await.unwrap()?; + Self::connection_added(&ctxt, &id, &path).await?; + Ok(path) + } + + /// Returns the D-Bus path of the network connection. + /// + /// * `id`: connection ID. + pub async fn get_connection(&self, id: &str) -> zbus::fdo::Result { + let objects = self.objects.lock().await; + match objects.connection_path(id) { + Some(path) => Ok(path.into()), + None => Err(NetworkStateError::UnknownConnection(id.to_string()).into()), + } + } + + /// Removes a network connection. + /// + /// * `uuid`: connection UUID.. + pub async fn remove_connection(&mut self, id: &str) -> zbus::fdo::Result<()> { + let actions = self.actions.lock().await; + actions + .send(Action::RemoveConnection(id.to_string())) + .unwrap(); + Ok(()) + } + + /// Applies the network configuration. + /// + /// It includes adding, updating and removing connections as needed. + pub async fn apply(&self) -> zbus::fdo::Result<()> { + let actions = self.actions.lock().await; + actions.send(Action::Apply).unwrap(); + Ok(()) + } + + /// Notifies than a new interface has been added. + #[dbus_interface(signal)] + pub async fn connection_added( + ctxt: &SignalContext<'_>, + id: &str, + path: &ObjectPath<'_>, + ) -> zbus::Result<()>; +} + +/// D-Bus interface for a network connection +/// +/// It offers an API to query a connection. +pub struct Connection { + actions: Arc>>, + uuid: Uuid, +} + +impl Connection { + /// Creates a Connection interface object. + /// + /// * `actions`: sending-half of a channel to send actions. + /// * `uuid`: network connection's UUID. + pub fn new(actions: UnboundedSender, uuid: Uuid) -> Self { + Self { + actions: Arc::new(Mutex::new(actions)), + uuid, + } + } +} + +#[dbus_interface(name = "org.opensuse.Agama1.Network.Connection")] +impl Connection { + /// Connection ID. + /// + /// Unique identifier of the network connection. It may or not be the same that the used by the + /// backend. For instance, when using NetworkManager (which is the only supported backend by + /// now), it uses the original ID but appending a number in case the ID is duplicated. + #[dbus_interface(property)] + pub async fn id(&self) -> zbus::fdo::Result { + let connection = self.get_connection().await?; + Ok(connection.id) + } + + /// Connection UUID. + /// + /// Unique identifier of the network connection. It may or not be the same that the used by the + /// backend. + #[dbus_interface(property)] + pub async fn uuid(&self) -> String { + self.uuid.to_string() + } + + #[dbus_interface(property)] + pub async fn controller(&self) -> zbus::fdo::Result { + let connection = self.get_connection().await?; + let result = match connection.controller { + Some(uuid) => uuid.to_string(), + None => "".to_string(), + }; + Ok(result) + } + + #[dbus_interface(property)] + pub async fn interface(&self) -> zbus::fdo::Result { + let connection = self.get_connection().await?; + Ok(connection.interface.unwrap_or_default()) + } + + #[dbus_interface(property)] + pub async fn set_interface(&mut self, name: &str) -> zbus::fdo::Result<()> { + let interface = Some(name.to_string()); + self.update_connection(|c| c.interface = interface).await?; + Ok(()) + } + + /// Custom mac-address + #[dbus_interface(property)] + pub async fn mac_address(&self) -> zbus::fdo::Result { + let connection = self.get_connection().await?; + Ok(connection.mac_address.to_string()) + } + + #[dbus_interface(property)] + pub async fn set_mac_address(&mut self, mac_address: &str) -> zbus::fdo::Result<()> { + let mac_address = MacAddress::from_str(mac_address)?; + self.update_connection(|c| c.mac_address = mac_address) + .await?; + Ok(()) + } + + /// Whether the network interface should be active or not + #[dbus_interface(property)] + pub async fn active(&self) -> zbus::fdo::Result { + let connection = self.get_connection().await?; + Ok(connection.is_up()) + } + + #[dbus_interface(property)] + pub async fn set_active(&mut self, active: bool) -> zbus::fdo::Result<()> { + self.update_connection(|c| { + if active { + c.set_up(); + } else { + c.set_down(); + } + }) + .await?; + Ok(()) + } +} + +#[async_trait] +impl ConnectionInterface for Connection { + fn uuid(&self) -> Uuid { + self.uuid + } + + async fn actions(&self) -> MutexGuard> { + self.actions.lock().await + } +} + +/// D-Bus interface for Match settings +pub struct Match { + actions: Arc>>, + uuid: Uuid, +} + +impl Match { + /// Creates a Match Settings interface object. + /// + /// * `actions`: sending-half of a channel to send actions. + /// * `uuid`: nework connection's UUID. + pub fn new(actions: UnboundedSender, uuid: Uuid) -> Self { + Self { + actions: Arc::new(Mutex::new(actions)), + uuid, + } + } +} + +#[dbus_interface(name = "org.opensuse.Agama1.Network.Connection.Match")] +impl Match { + /// List of driver names to match. + #[dbus_interface(property)] + pub async fn driver(&self) -> zbus::fdo::Result> { + let connection = self.get_connection().await?; + Ok(connection.match_config.driver) + } + + #[dbus_interface(property)] + pub async fn set_driver(&mut self, driver: Vec) -> zbus::fdo::Result<()> { + self.update_connection(|c| c.match_config.driver = driver) + .await?; + Ok(()) + } + + /// List of paths to match agains the ID_PATH udev property of devices. + #[dbus_interface(property)] + pub async fn path(&self) -> zbus::fdo::Result> { + let connection = self.get_connection().await?; + Ok(connection.match_config.path) + } + + #[dbus_interface(property)] + pub async fn set_path(&mut self, path: Vec) -> zbus::fdo::Result<()> { + self.update_connection(|c| c.match_config.path = path) + .await?; + Ok(()) + } + /// List of interface names to match. + #[dbus_interface(property)] + pub async fn interface(&self) -> zbus::fdo::Result> { + let connection = self.get_connection().await?; + Ok(connection.match_config.interface) + } + + #[dbus_interface(property)] + pub async fn set_interface(&mut self, interface: Vec) -> zbus::fdo::Result<()> { + self.update_connection(|c| c.match_config.interface = interface) + .await?; + Ok(()) + } + + /// List of kernel options to match. + #[dbus_interface(property)] + pub async fn kernel(&self) -> zbus::fdo::Result> { + let connection = self.get_connection().await?; + Ok(connection.match_config.kernel) + } + + #[dbus_interface(property)] + pub async fn set_kernel(&mut self, kernel: Vec) -> zbus::fdo::Result<()> { + self.update_connection(|c| c.match_config.kernel = kernel) + .await?; + Ok(()) + } +} + +#[async_trait] +impl ConnectionInterface for Match { + fn uuid(&self) -> Uuid { + self.uuid + } + + async fn actions(&self) -> MutexGuard> { + self.actions.lock().await + } +} diff --git a/rust/agama-dbus-server/src/network/dbus/interfaces/devices.rs b/rust/agama-dbus-server/src/network/dbus/interfaces/devices.rs new file mode 100644 index 0000000000..41339e01e5 --- /dev/null +++ b/rust/agama-dbus-server/src/network/dbus/interfaces/devices.rs @@ -0,0 +1,70 @@ +use crate::network::{dbus::ObjectsRegistry, model::Device as NetworkDevice}; +use std::sync::Arc; +use tokio::sync::Mutex; +use zbus::{dbus_interface, zvariant::ObjectPath}; + +/// D-Bus interface for the network devices collection +/// +/// It offers an API to query the devices collection. +pub struct Devices { + objects: Arc>, +} + +impl Devices { + /// Creates a Devices interface object. + /// + /// * `objects`: Objects paths registry. + pub fn new(objects: Arc>) -> Self { + Self { objects } + } +} + +#[dbus_interface(name = "org.opensuse.Agama1.Network.Devices")] +impl Devices { + /// Returns the D-Bus paths of the network devices. + pub async fn get_devices(&self) -> Vec { + let objects = self.objects.lock().await; + objects + .devices_paths() + .iter() + .filter_map(|c| ObjectPath::try_from(c.clone()).ok()) + .collect() + } +} + +/// D-Bus interface for a network device +/// +/// It offers an API to query basic networking devices information (e.g., the name). +pub struct Device { + device: NetworkDevice, +} + +impl Device { + /// Creates an interface object. + /// + /// * `device`: network device. + pub fn new(device: NetworkDevice) -> Self { + Self { device } + } +} + +#[dbus_interface(name = "org.opensuse.Agama1.Network.Device")] +impl Device { + /// Device name. + /// + /// Kernel device name, e.g., eth0, enp1s0, etc. + #[dbus_interface(property)] + pub fn name(&self) -> &str { + &self.device.name + } + + /// Device type. + /// + /// Possible values: 0 = loopback, 1 = ethernet, 2 = wireless. + /// + /// See [agama_lib::network::types::DeviceType]. + #[dbus_interface(property, name = "Type")] + pub fn device_type(&self) -> u8 { + self.device.type_ as u8 + } +} diff --git a/rust/agama-dbus-server/src/network/dbus/interfaces/ip_config.rs b/rust/agama-dbus-server/src/network/dbus/interfaces/ip_config.rs index a0051c75c6..dfbfd6ad30 100644 --- a/rust/agama-dbus-server/src/network/dbus/interfaces/ip_config.rs +++ b/rust/agama-dbus-server/src/network/dbus/interfaces/ip_config.rs @@ -6,71 +6,51 @@ //! to the `Ip` struct. use crate::network::{ action::Action, - model::{Connection as NetworkConnection, IpConfig, Ipv4Method, Ipv6Method}, + error::NetworkStateError, + model::{IpConfig, Ipv4Method, Ipv6Method}, }; +use async_trait::async_trait; use cidr::IpInet; use std::{net::IpAddr, sync::Arc}; use tokio::sync::mpsc::UnboundedSender; -use tokio::sync::{MappedMutexGuard, Mutex, MutexGuard}; +use tokio::sync::{Mutex, MutexGuard}; +use uuid::Uuid; use zbus::dbus_interface; +use super::common::ConnectionInterface; + /// D-Bus interface for IPv4 and IPv6 settings pub struct Ip { actions: Arc>>, - connection: Arc>, + uuid: Uuid, } impl Ip { /// Creates an IP interface object. /// /// * `actions`: sending-half of a channel to send actions. - /// * `connection`: connection to expose over D-Bus. - pub fn new( - actions: UnboundedSender, - connection: Arc>, - ) -> Self { + /// * `uuid`: connection UUID.. + pub fn new(actions: UnboundedSender, uuid: Uuid) -> Self { Self { actions: Arc::new(Mutex::new(actions)), - connection, + uuid, } } - /// Returns the underlying connection. - async fn get_connection(&self) -> MutexGuard { - self.connection.lock().await - } - - /// Updates the connection data in the NetworkSystem. - /// - /// * `connection`: Updated connection. - async fn update_connection<'a>( - &self, - connection: MutexGuard<'a, NetworkConnection>, - ) -> zbus::fdo::Result<()> { - let actions = self.actions.lock().await; - actions - .send(Action::UpdateConnection(connection.clone())) - .unwrap(); - Ok(()) - } -} - -impl Ip { /// Returns the IpConfig struct. - async fn get_ip_config(&self) -> MappedMutexGuard { - MutexGuard::map(self.get_connection().await, |c| &mut c.ip_config) + async fn get_ip_config(&self) -> Result { + self.get_connection().await.map(|c| c.ip_config) } /// Updates the IpConfig struct. /// /// * `func`: function to update the configuration. - async fn update_config(&self, func: F) -> zbus::fdo::Result<()> + async fn update_ip_config(&self, func: F) -> zbus::fdo::Result<()> where - F: Fn(&mut IpConfig), + F: Fn(&mut IpConfig) + std::marker::Send, { - let mut connection = self.get_connection().await; - func(&mut connection.ip_config); - self.update_connection(connection).await?; + self.update_connection(move |c| func(&mut c.ip_config)) + .await?; Ok(()) } } @@ -81,15 +61,16 @@ impl Ip { /// /// When the method is 'auto', these addresses are used as additional addresses. #[dbus_interface(property)] - pub async fn addresses(&self) -> Vec { - let ip_config = self.get_ip_config().await; - ip_config.addresses.iter().map(|a| a.to_string()).collect() + pub async fn addresses(&self) -> zbus::fdo::Result> { + let ip_config = self.get_ip_config().await?; + let addresses = ip_config.addresses.iter().map(|a| a.to_string()).collect(); + Ok(addresses) } #[dbus_interface(property)] pub async fn set_addresses(&mut self, addresses: Vec) -> zbus::fdo::Result<()> { let addresses = helpers::parse_addresses::(addresses); - self.update_config(|ip| ip.addresses = addresses.clone()) + self.update_ip_config(|ip| ip.addresses = addresses.clone()) .await } @@ -99,15 +80,15 @@ impl Ip { /// /// See [crate::network::model::Ipv4Method]. #[dbus_interface(property)] - pub async fn method4(&self) -> String { - let ip_config = self.get_ip_config().await; - ip_config.method4.to_string() + pub async fn method4(&self) -> zbus::fdo::Result { + let ip_config = self.get_ip_config().await?; + Ok(ip_config.method4.to_string()) } #[dbus_interface(property)] pub async fn set_method4(&mut self, method: &str) -> zbus::fdo::Result<()> { let method: Ipv4Method = method.parse()?; - self.update_config(|ip| ip.method4 = method).await + self.update_ip_config(|ip| ip.method4 = method).await } /// IPv6 configuration method. @@ -116,32 +97,33 @@ impl Ip { /// /// See [crate::network::model::Ipv6Method]. #[dbus_interface(property)] - pub async fn method6(&self) -> String { - let ip_config = self.get_ip_config().await; - ip_config.method6.to_string() + pub async fn method6(&self) -> zbus::fdo::Result { + let ip_config = self.get_ip_config().await?; + Ok(ip_config.method6.to_string()) } #[dbus_interface(property)] pub async fn set_method6(&mut self, method: &str) -> zbus::fdo::Result<()> { let method: Ipv6Method = method.parse()?; - self.update_config(|ip| ip.method6 = method).await + self.update_ip_config(|ip| ip.method6 = method).await } /// Name server addresses. #[dbus_interface(property)] - pub async fn nameservers(&self) -> Vec { - let ip_config = self.get_ip_config().await; - ip_config + pub async fn nameservers(&self) -> zbus::fdo::Result> { + let ip_config = self.get_ip_config().await?; + let nameservers = ip_config .nameservers .iter() .map(IpAddr::to_string) - .collect() + .collect(); + Ok(nameservers) } #[dbus_interface(property)] pub async fn set_nameservers(&mut self, addresses: Vec) -> zbus::fdo::Result<()> { let addresses = helpers::parse_addresses::(addresses); - self.update_config(|ip| ip.nameservers = addresses.clone()) + self.update_ip_config(|ip| ip.nameservers = addresses.clone()) .await } @@ -149,36 +131,38 @@ impl Ip { /// /// An empty string removes the current value. #[dbus_interface(property)] - pub async fn gateway4(&self) -> String { - let ip_config = self.get_ip_config().await; - match ip_config.gateway4 { + pub async fn gateway4(&self) -> zbus::fdo::Result { + let ip_config = self.get_ip_config().await?; + let gateway = match ip_config.gateway4 { Some(ref address) => address.to_string(), None => "".to_string(), - } + }; + Ok(gateway) } #[dbus_interface(property)] pub async fn set_gateway4(&mut self, gateway: String) -> zbus::fdo::Result<()> { let gateway = helpers::parse_gateway(gateway)?; - self.update_config(|ip| ip.gateway4 = gateway).await + self.update_ip_config(|ip| ip.gateway4 = gateway).await } /// Network gateway for IPv6. /// /// An empty string removes the current value. #[dbus_interface(property)] - pub async fn gateway6(&self) -> String { - let ip_config = self.get_ip_config().await; - match ip_config.gateway6 { + pub async fn gateway6(&self) -> zbus::fdo::Result { + let ip_config = self.get_ip_config().await?; + let result = match ip_config.gateway6 { Some(ref address) => address.to_string(), None => "".to_string(), - } + }; + Ok(result) } #[dbus_interface(property)] pub async fn set_gateway6(&mut self, gateway: String) -> zbus::fdo::Result<()> { let gateway = helpers::parse_gateway(gateway)?; - self.update_config(|ip| ip.gateway6 = gateway).await + self.update_ip_config(|ip| ip.gateway6 = gateway).await } } @@ -229,3 +213,14 @@ mod helpers { } } } + +#[async_trait] +impl ConnectionInterface for Ip { + fn uuid(&self) -> Uuid { + self.uuid + } + + async fn actions(&self) -> MutexGuard> { + self.actions.lock().await + } +} diff --git a/rust/agama-dbus-server/src/network/dbus/tree.rs b/rust/agama-dbus-server/src/network/dbus/tree.rs index 45568fcf42..d6080508d9 100644 --- a/rust/agama-dbus-server/src/network/dbus/tree.rs +++ b/rust/agama-dbus-server/src/network/dbus/tree.rs @@ -76,18 +76,16 @@ impl Tree { Ok(()) } - /// Adds a connection to the D-Bus tree. + /// Adds a connection to the D-Bus tree and returns the D-Bus path. /// - /// * `connection`: connection to add. + /// * `conn`: connection to add. /// * `notify`: whether to notify the added connection pub async fn add_connection( &self, conn: &mut Connection, - notify: bool, - ) -> Result<(), ServiceError> { + ) -> Result { let mut objects = self.objects.lock().await; - let orig_id = conn.id.to_owned(); let uuid = conn.uuid; let (id, path) = objects.register_connection(conn); if id != conn.id { @@ -95,24 +93,17 @@ impl Tree { } log::info!("Publishing network connection '{}'", id); - let cloned = Arc::new(Mutex::new(conn.clone())); self.add_interface( &path, - interfaces::Connection::new(self.actions.clone(), Arc::clone(&cloned)), + interfaces::Connection::new(self.actions.clone(), uuid), ) .await?; - self.add_interface( - &path, - interfaces::Ip::new(self.actions.clone(), Arc::clone(&cloned)), - ) - .await?; + self.add_interface(&path, interfaces::Ip::new(self.actions.clone(), uuid)) + .await?; - self.add_interface( - &path, - interfaces::Match::new(self.actions.clone(), Arc::clone(&cloned)), - ) - .await?; + self.add_interface(&path, interfaces::Match::new(self.actions.clone(), uuid)) + .await?; if let ConnectionConfig::Bond(_) = conn.config { self.add_interface(&path, interfaces::Bond::new(self.actions.clone(), uuid)) @@ -120,18 +111,11 @@ impl Tree { } if let ConnectionConfig::Wireless(_) = conn.config { - self.add_interface( - &path, - interfaces::Wireless::new(self.actions.clone(), Arc::clone(&cloned)), - ) - .await?; - } - - if notify { - self.notify_connection_added(&orig_id, &path).await?; + self.add_interface(&path, interfaces::Wireless::new(self.actions.clone(), uuid)) + .await?; } - Ok(()) + Ok(path.into()) } /// Removes a connection from the tree @@ -152,7 +136,7 @@ impl Tree { /// * `connections`: list of connections. async fn add_connections(&self, connections: &mut [Connection]) -> Result<(), ServiceError> { for conn in connections.iter_mut() { - self.add_connection(conn, false).await?; + self.add_connection(conn).await?; } self.add_interface( @@ -208,19 +192,6 @@ impl Tree { let object_server = self.connection.object_server(); Ok(object_server.at(path, iface).await?) } - - /// Notify that a new connection has been added - async fn notify_connection_added( - &self, - id: &str, - path: &ObjectPath<'_>, - ) -> Result<(), ServiceError> { - let object_server = self.connection.object_server(); - let iface_ref = object_server - .interface::<_, interfaces::Connections>(CONNECTIONS_PATH) - .await?; - Ok(interfaces::Connections::connection_added(iface_ref.signal_context(), id, path).await?) - } } /// Objects paths for known devices and connections diff --git a/rust/agama-dbus-server/src/network/error.rs b/rust/agama-dbus-server/src/network/error.rs index e3873eaa27..6b674b7efb 100644 --- a/rust/agama-dbus-server/src/network/error.rs +++ b/rust/agama-dbus-server/src/network/error.rs @@ -27,6 +27,8 @@ pub enum NetworkStateError { InvalidBondOptions, #[error("Not a controller connection: '{0}'")] NotControllerConnection(String), + #[error("Unexpected configuration")] + UnexpectedConfiguration, } impl From for zbus::fdo::Error { diff --git a/rust/agama-dbus-server/src/network/model.rs b/rust/agama-dbus-server/src/network/model.rs index e37237f381..0205b2fe2b 100644 --- a/rust/agama-dbus-server/src/network/model.rs +++ b/rust/agama-dbus-server/src/network/model.rs @@ -412,6 +412,18 @@ pub enum ConnectionConfig { Bond(BondConfig), } +impl From for ConnectionConfig { + fn from(value: BondConfig) -> Self { + Self::Bond(value) + } +} + +impl From for ConnectionConfig { + fn from(value: WirelessConfig) -> Self { + Self::Wireless(value) + } +} + #[derive(Debug, Error)] #[error("Invalid MAC address: {0}")] pub struct InvalidMacAddress(String); @@ -613,6 +625,17 @@ pub struct WirelessConfig { pub security: SecurityProtocol, } +impl TryFrom for WirelessConfig { + type Error = NetworkStateError; + + fn try_from(value: ConnectionConfig) -> Result { + match value { + ConnectionConfig::Wireless(config) => Ok(config), + _ => Err(NetworkStateError::UnexpectedConfiguration), + } + } +} + #[derive(Debug, Default, Clone, Copy, PartialEq)] pub enum WirelessMode { Unknown = 0, @@ -735,3 +758,14 @@ pub struct BondConfig { pub mode: BondMode, pub options: BondOptions, } + +impl TryFrom for BondConfig { + type Error = NetworkStateError; + + fn try_from(value: ConnectionConfig) -> Result { + match value { + ConnectionConfig::Bond(config) => Ok(config), + _ => Err(NetworkStateError::UnexpectedConfiguration), + } + } +} diff --git a/rust/agama-dbus-server/src/network/system.rs b/rust/agama-dbus-server/src/network/system.rs index 4500c3be43..b0850064e5 100644 --- a/rust/agama-dbus-server/src/network/system.rs +++ b/rust/agama-dbus-server/src/network/system.rs @@ -1,8 +1,10 @@ use super::error::NetworkStateError; use crate::network::{dbus::Tree, model::Connection, Action, Adapter, NetworkState}; +use agama_lib::network::types::DeviceType; use std::error::Error; use tokio::sync::mpsc::{self, UnboundedReceiver, UnboundedSender}; use uuid::Uuid; +use zbus::zvariant::OwnedObjectPath; /// Represents the network system using holding the state and setting up the D-Bus tree. pub struct NetworkSystem { @@ -67,25 +69,24 @@ impl NetworkSystem { /// Dispatch an action. pub async fn dispatch_action(&mut self, action: Action) -> Result<(), Box> { match action { - Action::AddConnection(name, ty) => { - let mut conn = Connection::new(name, ty); - self.tree.add_connection(&mut conn, true).await?; - self.state.add_connection(conn)?; + Action::AddConnection(name, ty, tx) => { + let result = self.add_connection_action(name, ty).await; + tx.send(result).unwrap(); } - Action::GetConnection(uuid, rx) => { + Action::GetConnection(uuid, tx) => { let conn = self.state.get_connection_by_uuid(uuid); - rx.send(conn.cloned()).unwrap(); + tx.send(conn.cloned()).unwrap(); } - Action::GetController(uuid, rx) => { + Action::GetController(uuid, tx) => { let result = self.get_controller_action(uuid); - rx.send(result).unwrap() + tx.send(result).unwrap() } Action::SetPorts(uuid, ports, rx) => { - let result = self.set_ports_action(uuid, ports); + let result = self.set_ports_action(uuid, *ports); rx.send(result).unwrap(); } Action::UpdateConnection(conn) => { - self.state.update_connection(conn)?; + self.state.update_connection(*conn)?; } Action::RemoveConnection(id) => { self.tree.remove_connection(&id).await?; @@ -104,6 +105,22 @@ impl NetworkSystem { Ok(()) } + async fn add_connection_action( + &mut self, + name: String, + ty: DeviceType, + ) -> Result { + let mut conn = Connection::new(name, ty); + // TODO: handle tree handling problems + let path = self + .tree + .add_connection(&mut conn) + .await + .expect("Could not update the D-Bus tree"); + self.state.add_connection(conn)?; + Ok(path) + } + fn set_ports_action( &mut self, uuid: Uuid, diff --git a/rust/agama-lib/src/network/proxies.rs b/rust/agama-lib/src/network/proxies.rs index a3b1916abf..77aed57feb 100644 --- a/rust/agama-lib/src/network/proxies.rs +++ b/rust/agama-lib/src/network/proxies.rs @@ -37,7 +37,7 @@ trait Connections { /// /// `name`: connection name. /// `ty`: connection type. - fn add_connection(&self, name: &str, ty: u8) -> zbus::Result<()>; + fn add_connection(&self, name: &str, ty: u8) -> zbus::Result; /// Apply method fn apply(&self) -> zbus::Result<()>; diff --git a/rust/rustfmt.toml b/rust/rustfmt.toml new file mode 100644 index 0000000000..3a26366d4d --- /dev/null +++ b/rust/rustfmt.toml @@ -0,0 +1 @@ +edition = "2021"