diff --git a/rust/agama-dbus-server/src/network/dbus/interfaces.rs b/rust/agama-dbus-server/src/network/dbus/interfaces.rs index fb92b711de..4dbc4a9d54 100644 --- a/rust/agama-dbus-server/src/network/dbus/interfaces.rs +++ b/rust/agama-dbus-server/src/network/dbus/interfaces.rs @@ -177,21 +177,41 @@ impl Connections { /// /// 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(connection: Arc>) -> Self { - Self { connection } + pub fn new(actions: Sender, 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())) + .await + .unwrap(); + Ok(()) + } } #[dbus_interface(name = "org.opensuse.Agama1.Network.Connection")] @@ -205,6 +225,119 @@ impl Connection { pub async fn id(&self) -> String { self.get_connection().await.id().to_string() } + + #[dbus_interface(property)] + pub async fn interface(&self) -> String { + self.get_connection().await.interface().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.set_interface(name); + 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: Sender, 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())) + .await + .unwrap(); + Ok(()) + } +} + +#[dbus_interface(name = "org.opensuse.Agama1.Network.Connection.Match")] +impl Match { + /// List of driver + #[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; + let config = connection.match_config_mut(); + config.driver = driver; + self.update_connection(connection).await + } + + /// List of paths + #[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; + let config = connection.match_config_mut(); + config.path = path; + self.update_connection(connection).await + } + /// List of driver + #[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; + let config = connection.match_config_mut(); + config.interface = interface; + self.update_connection(connection).await + } + + /// List of kernel options + #[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; + let config = connection.match_config_mut(); + config.kernel = kernel; + self.update_connection(connection).await + } } /// D-Bus interface for IPv4 settings diff --git a/rust/agama-dbus-server/src/network/dbus/tree.rs b/rust/agama-dbus-server/src/network/dbus/tree.rs index e299b3a78f..bcf5f097b3 100644 --- a/rust/agama-dbus-server/src/network/dbus/tree.rs +++ b/rust/agama-dbus-server/src/network/dbus/tree.rs @@ -95,8 +95,11 @@ impl Tree { log::info!("Publishing network connection '{}'", id); let cloned = Arc::new(Mutex::new(conn.clone())); - self.add_interface(&path, interfaces::Connection::new(Arc::clone(&cloned))) - .await?; + self.add_interface( + &path, + interfaces::Connection::new(self.actions.clone(), Arc::clone(&cloned)), + ) + .await?; self.add_interface( &path, @@ -104,6 +107,11 @@ impl Tree { ) .await?; + self.add_interface( + &path, + interfaces::Match::new(self.actions.clone(), Arc::clone(&cloned)), + ) + .await?; if let Connection::Wireless(_) = conn { self.add_interface( &path, diff --git a/rust/agama-dbus-server/src/network/model.rs b/rust/agama-dbus-server/src/network/model.rs index 43c6ef25c7..7478e546de 100644 --- a/rust/agama-dbus-server/src/network/model.rs +++ b/rust/agama-dbus-server/src/network/model.rs @@ -262,6 +262,14 @@ impl Connection { self.base_mut().id = id.to_string() } + pub fn interface(&self) -> &str { + self.base().interface.as_str() + } + + pub fn set_interface(&mut self, interface: &str) { + self.base_mut().interface = interface.to_string() + } + pub fn uuid(&self) -> Uuid { self.base().uuid } @@ -274,6 +282,14 @@ impl Connection { &mut self.base_mut().ipv4 } + pub fn match_config(&self) -> &MatchConfig { + &self.base().match_config + } + + pub fn match_config_mut(&mut self) -> &mut MatchConfig { + &mut self.base_mut().match_config + } + pub fn remove(&mut self) { self.base_mut().status = Status::Removed; } @@ -294,6 +310,8 @@ pub struct BaseConnection { pub uuid: Uuid, pub ipv4: Ipv4Config, pub status: Status, + pub interface: String, + pub match_config: MatchConfig, } impl PartialEq for BaseConnection { @@ -317,6 +335,14 @@ pub struct Ipv4Config { pub gateway: Option, } +#[derive(Debug, Default, PartialEq, Clone)] +pub struct MatchConfig { + pub driver: Vec, + pub interface: Vec, + pub path: Vec, + pub kernel: Vec, +} + #[derive(Debug, Error)] #[error("Unknown IP configuration method name: {0}")] pub struct UnknownIpMethod(String); diff --git a/rust/agama-dbus-server/src/network/nm/dbus.rs b/rust/agama-dbus-server/src/network/nm/dbus.rs index 7511b44399..9e513c0898 100644 --- a/rust/agama-dbus-server/src/network/nm/dbus.rs +++ b/rust/agama-dbus-server/src/network/nm/dbus.rs @@ -19,12 +19,16 @@ const LOOPBACK_KEY: &str = "loopback"; /// Converts a connection struct into a HashMap that can be sent over D-Bus. /// -/// * `conn`: Connection to cnvert. +/// * `conn`: Connection to convert. pub fn connection_to_dbus(conn: &Connection) -> NestedHash { let mut result = NestedHash::new(); - let mut connection_dbus = - HashMap::from([("id", conn.id().into()), ("type", ETHERNET_KEY.into())]); + let mut connection_dbus = HashMap::from([ + ("id", conn.id().into()), + ("type", ETHERNET_KEY.into()), + ("interface-name", conn.interface().into()), + ]); result.insert("ipv4", ipv4_to_dbus(conn.ipv4())); + result.insert("match", match_config_to_dbus(conn.match_config())); if let Connection::Wireless(wireless) = conn { connection_dbus.insert("type", "802-11-wireless".into()); @@ -33,6 +37,7 @@ pub fn connection_to_dbus(conn: &Connection) -> NestedHash { result.insert(k, v); } } + result.insert("connection", connection_dbus); result } @@ -96,6 +101,15 @@ pub fn merge_dbus_connections<'a>( /// /// * `conn`: connection represented as a NestedHash. fn cleanup_dbus_connection(conn: &mut NestedHash) { + if let Some(connection) = conn.get_mut("connection") { + if connection + .get("interface-name") + .is_some_and(|v| is_empty_value(&v)) + { + connection.remove("interface-name"); + } + } + if let Some(ipv4) = conn.get_mut("ipv4") { ipv4.remove("addresses"); ipv4.remove("dns"); @@ -159,6 +173,41 @@ fn wireless_config_to_dbus(conn: &WirelessConnection) -> NestedHash { ]) } +/// Converts a MatchConfig struct into a HashMap that can be sent over D-Bus. +/// +/// * `match_config`: MatchConfig to convert. +fn match_config_to_dbus(match_config: &MatchConfig) -> HashMap<&str, zvariant::Value> { + let drivers: Value = match_config + .driver + .iter() + .cloned() + .collect::>() + .into(); + + let kernels: Value = match_config + .kernel + .iter() + .cloned() + .collect::>() + .into(); + + let paths: Value = match_config.path.iter().cloned().collect::>().into(); + + let interfaces: Value = match_config + .interface + .iter() + .cloned() + .collect::>() + .into(); + + HashMap::from([ + ("driver", drivers), + ("kernel-command-line", kernels), + ("path", paths), + ("interface-name", interfaces), + ]) +} + fn base_connection_from_dbus(conn: &OwnedNestedHash) -> Option { let Some(connection) = conn.get("connection") else { return None; @@ -173,6 +222,15 @@ fn base_connection_from_dbus(conn: &OwnedNestedHash) -> Option { ..Default::default() }; + if let Some(interface) = connection.get("interface-name") { + let interface: &str = interface.downcast_ref()?; + base_connection.interface = interface.to_string(); + } + + if let Some(match_config) = conn.get("match") { + base_connection.match_config = match_config_from_dbus(match_config)?; + } + if let Some(ipv4) = conn.get("ipv4") { base_connection.ipv4 = ipv4_config_from_dbus(ipv4)?; } @@ -180,6 +238,46 @@ fn base_connection_from_dbus(conn: &OwnedNestedHash) -> Option { Some(base_connection) } +fn match_config_from_dbus( + match_config: &HashMap, +) -> Option { + let mut match_conf = MatchConfig::default(); + + if let Some(drivers) = match_config.get("driver") { + let drivers = drivers.downcast_ref::()?; + for driver in drivers.get() { + let driver: &str = driver.downcast_ref()?; + match_conf.driver.push(driver.to_string()); + } + } + + if let Some(interface_names) = match_config.get("interface-name") { + let interface_names = interface_names.downcast_ref::()?; + for name in interface_names.get() { + let name: &str = name.downcast_ref()?; + match_conf.interface.push(name.to_string()); + } + } + + if let Some(paths) = match_config.get("path") { + let paths = paths.downcast_ref::()?; + for path in paths.get() { + let path: &str = path.downcast_ref()?; + match_conf.path.push(path.to_string()); + } + } + + if let Some(kernel_options) = match_config.get("kernel-command-line") { + let options = kernel_options.downcast_ref::()?; + for option in options.get() { + let option: &str = option.downcast_ref()?; + match_conf.kernel.push(option.to_string()); + } + } + + Some(match_conf) +} + fn ipv4_config_from_dbus(ipv4: &HashMap) -> Option { let method: &str = ipv4.get("method")?.downcast_ref()?; let address_data = ipv4.get("address-data")?; @@ -241,6 +339,19 @@ fn wireless_config_from_dbus(conn: &OwnedNestedHash) -> Option { Some(wireless_config) } +/// Determines whether a value is empty. +/// +/// TODO: Generalize for other kind of values, like dicts or arrays. +/// +/// * `value`: value to analyze +fn is_empty_value(value: &zvariant::Value) -> bool { + if let Some(value) = value.downcast_ref::() { + return value.is_empty(); + } + + false +} + #[cfg(test)] mod test { use super::{ @@ -279,9 +390,15 @@ mod test { ), ]); + let match_section = HashMap::from([( + "kernel-command-line".to_string(), + Value::new(vec!["pci-0000:00:19.0"]).to_owned(), + )]); + let dbus_conn = HashMap::from([ ("connection".to_string(), connection_section), ("ipv4".to_string(), ipv4_section), + ("match".to_string(), match_section), (ETHERNET_KEY.to_string(), build_ethernet_section_from_dbus()), ]); @@ -289,6 +406,8 @@ mod test { assert_eq!(connection.id(), "eth0"); let ipv4 = connection.ipv4(); + let match_config = connection.match_config(); + assert_eq!(match_config.kernel, vec!["pci-0000:00:19.0"]); assert_eq!(ipv4.addresses, vec!["192.168.0.10/24".parse().unwrap()]); assert_eq!(ipv4.nameservers, vec![Ipv4Addr::new(192, 168, 0, 2)]); assert_eq!(ipv4.method, IpMethod::Auto); @@ -404,6 +523,7 @@ mod test { let base = BaseConnection { id: "agama".to_string(), + interface: "eth0".to_string(), ..Default::default() }; let ethernet = EthernetConnection { @@ -420,6 +540,11 @@ mod test { Value::new("agama".to_string()) ); + assert_eq!( + *connection.get("interface-name").unwrap(), + Value::new("eth0".to_string()) + ); + let ipv4 = merged.get("ipv4").unwrap(); assert_eq!( *ipv4.get("method").unwrap(), @@ -432,6 +557,31 @@ mod test { assert!(ipv4.get("addresses").is_none()); } + #[test] + fn test_merged_connections_are_clean() { + let mut original = OwnedNestedHash::new(); + let connection = HashMap::from([ + ("id".to_string(), Value::new("conn0".to_string()).to_owned()), + ( + "type".to_string(), + Value::new(ETHERNET_KEY.to_string()).to_owned(), + ), + ( + "interface-name".to_string(), + Value::new("eth0".to_string()).to_owned(), + ), + ]); + original.insert("connection".to_string(), connection); + + let mut updated = Connection::Ethernet(EthernetConnection::default()); + updated.set_interface(""); + let updated = connection_to_dbus(&updated); + + let merged = merge_dbus_connections(&original, &updated); + let connection = merged.get("connection").unwrap(); + assert_eq!(connection.get("interface-name"), None); + } + fn build_ethernet_section_from_dbus() -> HashMap { HashMap::from([("auto-negotiate".to_string(), true.into())]) } diff --git a/rust/agama-dbus-server/tests/network.rs b/rust/agama-dbus-server/tests/network.rs index cef72b000f..e473fbfc7d 100644 --- a/rust/agama-dbus-server/tests/network.rs +++ b/rust/agama-dbus-server/tests/network.rs @@ -78,3 +78,29 @@ async fn test_add_connection() { assert_eq!(conn.id, "wlan0"); assert_eq!(conn.device_type(), DeviceType::Wireless); } + +#[test] +async fn test_update_connection() { + let mut server = DBusServer::new().start().await; + + let device = model::Device { + name: String::from("eth0"), + type_: DeviceType::Ethernet, + }; + let eth0 = model::Connection::new("eth0".to_string(), DeviceType::Ethernet); + let state = NetworkState::new(vec![device], vec![eth0]); + let adapter = NetworkTestAdapter(state); + + let _service = NetworkService::start(&server.connection(), adapter) + .await + .unwrap(); + + server.request_name().await.unwrap(); + + let client = NetworkClient::new(server.connection()).await.unwrap(); + let mut dbus_eth0 = client.get_connection("eth0").await.unwrap(); + dbus_eth0.interface = Some("eth0".to_string()); + client.add_or_update_connection(&dbus_eth0).await.unwrap(); + let dbus_eth0 = client.get_connection("eth0").await.unwrap(); + assert_eq!(dbus_eth0.interface, Some("eth0".to_string())); +} diff --git a/rust/agama-lib/share/examples/profile.json b/rust/agama-lib/share/examples/profile.json index 12dad32cde..bc379d9153 100644 --- a/rust/agama-lib/share/examples/profile.json +++ b/rust/agama-lib/share/examples/profile.json @@ -27,6 +27,7 @@ { "id": "Ethernet network device 1", "method": "manual", + "interface": "eth0", "addresses": [ "192.168.122.100/24" ], diff --git a/rust/agama-lib/share/examples/profile.jsonnet b/rust/agama-lib/share/examples/profile.jsonnet index 96095a0697..b893150f27 100644 --- a/rust/agama-lib/share/examples/profile.jsonnet +++ b/rust/agama-lib/share/examples/profile.jsonnet @@ -63,7 +63,10 @@ local findBiggestDisk(disks) = ], nameservers: [ '1.2.3.4' - ] + ], + match: { + path: ["pci-0000:00:19.0"] + } } ] } diff --git a/rust/agama-lib/share/profile.schema.json b/rust/agama-lib/share/profile.schema.json index e49ea4e43f..842dd0f13b 100644 --- a/rust/agama-lib/share/profile.schema.json +++ b/rust/agama-lib/share/profile.schema.json @@ -32,6 +32,10 @@ "description": "Connection ID", "type": "string" }, + "interface": { + "description": "The name of the network interface bound to this connection", + "type": "string" + }, "method": { "description": "IPv4 configuration method (e.g., 'auto')", "type": "string", @@ -86,6 +90,45 @@ ] } } + }, + "match": { + "type": "object", + "description": "Match settings", + "additionalProperties": false, + "properties": { + "kernel": { + "type": "array", + "items": { + "description": "A list of kernel command line arguments to match", + "type": "string", + "additionalProperties": false + } + }, + "interface": { + "type": "array", + "items": { + "description": "A list of interface names to match", + "type": "string", + "additionalProperties": false + } + }, + "driver": { + "type": "array", + "items": { + "description": "A list of driver names to match", + "type": "string", + "additionalProperties": false + } + }, + "path": { + "type": "array", + "items": { + "description": "A list of paths to match against the ID_PATH udev property of devices", + "type": "string", + "additionalProperties": false + } + } + } } }, "required": [ diff --git a/rust/agama-lib/src/network/client.rs b/rust/agama-lib/src/network/client.rs index 240d0359f0..61be850280 100644 --- a/rust/agama-lib/src/network/client.rs +++ b/rust/agama-lib/src/network/client.rs @@ -1,8 +1,8 @@ -use super::settings::{NetworkConnection, WirelessSettings}; +use super::settings::{MatchSettings, NetworkConnection, WirelessSettings}; use super::types::SSID; use crate::error::ServiceError; -use super::proxies::{ConnectionProxy, ConnectionsProxy, IPv4Proxy, WirelessProxy}; +use super::proxies::{ConnectionProxy, ConnectionsProxy, IPv4Proxy, MatchProxy, WirelessProxy}; use async_std::stream::StreamExt; use zbus::zvariant::OwnedObjectPath; use zbus::Connection; @@ -21,6 +21,11 @@ impl<'a> NetworkClient<'a> { }) } + pub async fn get_connection(&self, id: &str) -> Result { + let path = self.connections_proxy.get_connection(id).await?; + Ok(self.connection_from(path.as_str()).await?) + } + /// Returns an array of network connections pub async fn connections(&self) -> Result, ServiceError> { let connection_paths = self.connections_proxy.get_connections().await?; @@ -33,6 +38,11 @@ impl<'a> NetworkClient<'a> { connection.wireless = Some(wireless); } + let match_settings = self.match_settings_from(path.as_str()).await?; + if !match_settings.is_empty() { + connection.match_settings = Some(match_settings); + } + connections.push(connection); } @@ -54,6 +64,10 @@ impl<'a> NetworkClient<'a> { .build() .await?; let id = connection_proxy.id().await?; + let interface = match connection_proxy.interface().await?.as_str() { + "" => None, + value => Some(value.to_string()), + }; let ipv4_proxy = IPv4Proxy::builder(&self.connection) .path(path)? @@ -74,6 +88,7 @@ impl<'a> NetworkClient<'a> { gateway, addresses, nameservers, + interface, ..Default::default() }) } @@ -96,6 +111,23 @@ impl<'a> NetworkClient<'a> { Ok(wireless) } + /// Returns the [match settings][MatchSettings] for the given connection + /// + /// * `path`: the connections path to get the match settings from + async fn match_settings_from(&self, path: &str) -> Result { + let match_proxy = MatchProxy::builder(&self.connection) + .path(path)? + .build() + .await?; + let match_settings = MatchSettings { + path: match_proxy.path().await?, + kernel: match_proxy.kernel().await?, + interface: match_proxy.interface().await?, + driver: match_proxy.driver().await?, + }; + Ok(match_settings) + } + /// Adds or updates a network connection. /// /// If a network connection with the same name exists, it updates its settings. Otherwise, it @@ -146,6 +178,36 @@ impl<'a> NetworkClient<'a> { &self, path: &OwnedObjectPath, conn: &NetworkConnection, + ) -> Result<(), ServiceError> { + let proxy = ConnectionProxy::builder(&self.connection) + .path(path)? + .build() + .await?; + + let interface = conn.interface.as_deref().unwrap_or(""); + proxy.set_interface(interface).await?; + + self.update_ipv4_settings(path, conn).await?; + + if let Some(ref wireless) = conn.wireless { + self.update_wireless_settings(path, wireless).await?; + } + + if let Some(ref match_settings) = conn.match_settings { + self.update_match_settings(path, match_settings).await?; + } + + Ok(()) + } + + /// Updates the IPv4 setttings for the network connection. + /// + /// * `path`: connection D-Bus path. + /// * `conn`: network connection. + async fn update_ipv4_settings( + &self, + path: &OwnedObjectPath, + conn: &NetworkConnection, ) -> Result<(), ServiceError> { let proxy = IPv4Proxy::builder(&self.connection) .path(path)? @@ -165,9 +227,6 @@ impl<'a> NetworkClient<'a> { let gateway = conn.gateway.as_deref().unwrap_or(""); proxy.set_gateway(gateway).await?; - if let Some(ref wireless) = conn.wireless { - self.update_wireless_settings(path, wireless).await?; - } Ok(()) } @@ -193,4 +252,24 @@ impl<'a> NetworkClient<'a> { proxy.set_password(&wireless.password).await?; Ok(()) } + + /// Updates the match settings for network connection. + /// + /// * `path`: connection D-Bus path. + /// * `match_settings`: match settings of the network connection. + async fn update_match_settings( + &self, + path: &OwnedObjectPath, + match_settings: &MatchSettings, + ) -> Result<(), ServiceError> { + let proxy = MatchProxy::builder(&self.connection) + .path(path)? + .build() + .await?; + + let paths: Vec<_> = match_settings.path.iter().map(String::as_ref).collect(); + proxy.set_path(paths.as_slice()).await?; + + Ok(()) + } } diff --git a/rust/agama-lib/src/network/proxies.rs b/rust/agama-lib/src/network/proxies.rs index 1456e286c5..4de94c5560 100644 --- a/rust/agama-lib/src/network/proxies.rs +++ b/rust/agama-lib/src/network/proxies.rs @@ -79,6 +79,10 @@ trait Connection { /// Id property #[dbus_proxy(property)] fn id(&self) -> zbus::Result; + #[dbus_proxy(property)] + fn interface(&self) -> zbus::Result; + #[dbus_proxy(property)] + fn set_interface(&self, interface: &str) -> zbus::Result<()>; } #[dbus_proxy( @@ -115,3 +119,28 @@ trait IPv4 { #[dbus_proxy(property)] fn set_nameservers(&self, value: &[&str]) -> zbus::Result<()>; } + +#[dbus_proxy( + interface = "org.opensuse.Agama1.Network.Connection.Match", + default_service = "org.opensuse.Agama1", + default_path = "/org/opensuse/Agama1/Network" +)] +trait Match { + /// Driver property + #[dbus_proxy(property)] + fn driver(&self) -> zbus::Result>; + + /// Interface property + #[dbus_proxy(property)] + fn interface(&self) -> zbus::Result>; + + /// Path property + #[dbus_proxy(property)] + fn path(&self) -> zbus::Result>; + #[dbus_proxy(property)] + fn set_path(&self, value: &[&str]) -> zbus::Result<()>; + + /// Path property + #[dbus_proxy(property)] + fn kernel(&self) -> zbus::Result>; +} diff --git a/rust/agama-lib/src/network/settings.rs b/rust/agama-lib/src/network/settings.rs index 04316d95ba..0ff53aa7cb 100644 --- a/rust/agama-lib/src/network/settings.rs +++ b/rust/agama-lib/src/network/settings.rs @@ -16,6 +16,27 @@ pub struct NetworkSettings { pub connections: Vec, } +#[derive(Clone, Debug, Default, Serialize, Deserialize)] +pub struct MatchSettings { + #[serde(skip_serializing_if = "Vec::is_empty", default)] + pub driver: Vec, + #[serde(skip_serializing_if = "Vec::is_empty", default)] + pub path: Vec, + #[serde(skip_serializing_if = "Vec::is_empty", default)] + pub kernel: Vec, + #[serde(skip_serializing_if = "Vec::is_empty", default)] + pub interface: Vec, +} + +impl MatchSettings { + pub fn is_empty(&self) -> bool { + self.path.is_empty() + && self.driver.is_empty() + && self.kernel.is_empty() + && self.interface.is_empty() + } +} + #[derive(Clone, Debug, Default, Serialize, Deserialize)] pub struct WirelessSettings { #[serde(skip_serializing_if = "String::is_empty")] @@ -38,6 +59,10 @@ pub struct NetworkConnection { pub nameservers: Vec, #[serde(skip_serializing_if = "Option::is_none")] pub wireless: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub interface: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub match_settings: Option, } impl NetworkConnection { diff --git a/rust/package/agama-cli.changes b/rust/package/agama-cli.changes index cde2101d02..9999eb7733 100644 --- a/rust/package/agama-cli.changes +++ b/rust/package/agama-cli.changes @@ -1,3 +1,9 @@ +------------------------------------------------------------------- +Wed Sep 13 09:27:22 UTC 2023 - Knut Anderssen + +- Allow to bind a connection to an specific interface through its + name or through a set of match settings (gh#opensSUSE/agama#723). + ------------------------------------------------------------------- Thu Aug 31 10:30:28 UTC 2023 - Imobach Gonzalez Sosa