diff --git a/rust/migrate-wicked/Cargo.lock b/rust/migrate-wicked/Cargo.lock index 7f17f4f30a..635f933082 100644 --- a/rust/migrate-wicked/Cargo.lock +++ b/rust/migrate-wicked/Cargo.lock @@ -1251,6 +1251,7 @@ dependencies = [ "cidr", "clap", "log", + "macaddr", "quick-xml", "regex", "serde", diff --git a/rust/migrate-wicked/Cargo.toml b/rust/migrate-wicked/Cargo.toml index 3c383b65ca..ca04ac59bd 100644 --- a/rust/migrate-wicked/Cargo.toml +++ b/rust/migrate-wicked/Cargo.toml @@ -25,6 +25,7 @@ tokio = { version = "1.33.0", features = ["macros", "rt-multi-thread"] } serde_ignored = "0.1.9" uuid = { version = "1.3.4", features = ["v4"] } async-trait = "0.1.77" +macaddr = "1.0" [[bin]] name = "migrate-wicked" diff --git a/rust/migrate-wicked/src/interface.rs b/rust/migrate-wicked/src/interface.rs index 13c92359fc..a5efd5ae33 100644 --- a/rust/migrate-wicked/src/interface.rs +++ b/rust/migrate-wicked/src/interface.rs @@ -1,4 +1,5 @@ use crate::bond::Bond; +use crate::wireless::Wireless; use crate::MIGRATION_SETTINGS; use agama_dbus_server::network::model::{ self, IpConfig, IpRoute, Ipv4Method, Ipv6Method, MacAddress, @@ -27,6 +28,8 @@ pub struct Interface { pub dummy: Option, pub ethernet: Option, pub bond: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub wireless: Option, #[serde(rename = "@origin")] pub origin: String, } @@ -152,6 +155,20 @@ impl Interface { connection.mac_address = MacAddress::try_from(&bond.address)?; connection.config = bond.into(); connections.push(connection); + } else if let Some(wireless) = &self.wireless { + if let Some(networks) = &wireless.networks { + if networks.len() > 1 { + log::info!("{} has multiple networks defined, these will be split into different connections in NM", connection.id); + } + for (i, network) in networks.iter().enumerate() { + let mut wireless_connection = connection.clone(); + if networks.len() > 1 { + wireless_connection.id.push_str(&format!("-{}", i)); + } + wireless_connection.config = network.try_into()?; + connections.push(wireless_connection); + } + } } else { connections.push(connection); } diff --git a/rust/migrate-wicked/src/main.rs b/rust/migrate-wicked/src/main.rs index cf7cafd167..e6e42ddf75 100644 --- a/rust/migrate-wicked/src/main.rs +++ b/rust/migrate-wicked/src/main.rs @@ -2,6 +2,7 @@ mod bond; mod interface; mod migrate; mod reader; +mod wireless; use clap::builder::TypedValueParser; use clap::{Args, Parser, Subcommand}; diff --git a/rust/migrate-wicked/src/wireless.rs b/rust/migrate-wicked/src/wireless.rs new file mode 100644 index 0000000000..abf7312d18 --- /dev/null +++ b/rust/migrate-wicked/src/wireless.rs @@ -0,0 +1,173 @@ +use crate::MIGRATION_SETTINGS; +use agama_dbus_server::network::model::{self, WepAuthAlg, WepKeyType, WepSecurity}; +use agama_lib::network::types::SSID; +use anyhow::anyhow; +use macaddr::MacAddr6; +use serde::{Deserialize, Deserializer, Serialize}; +use serde_with::formats::CommaSeparator; +use serde_with::StringWithSeparator; +use serde_with::{serde_as, skip_serializing_none, DeserializeFromStr, SerializeDisplay}; +use std::str::FromStr; +use strum_macros::{Display, EnumString}; + +#[skip_serializing_none] +#[derive(Debug, PartialEq, Serialize, Deserialize)] +pub struct Wireless { + #[serde(rename = "ap-scan")] + pub ap_scan: u32, + #[serde(default)] + #[serde(deserialize_with = "unwrap_wireless_networks")] + pub networks: Option>, +} + +#[serde_as] +#[skip_serializing_none] +#[derive(Debug, PartialEq, Serialize, Deserialize)] +pub struct Network { + pub essid: String, + #[serde(rename = "scan-ssid")] + pub scan_ssid: bool, + pub mode: WickedWirelessMode, + #[serde(rename = "wpa-psk")] + pub wpa_psk: Option, + #[serde(default)] + #[serde(rename = "key-management")] + #[serde_as(as = "StringWithSeparator::")] + pub key_management: Vec, + pub channel: Option, + #[serde(rename = "access-point")] + pub access_point: Option, + pub wep: Option, +} + +#[derive(Default, Debug, PartialEq, SerializeDisplay, DeserializeFromStr, EnumString, Display)] +#[strum(serialize_all = "kebab-case")] +pub enum WickedWirelessMode { + AdHoc = 0, + #[default] + Infrastructure = 1, + AP = 2, +} + +impl From<&WickedWirelessMode> for model::WirelessMode { + fn from(value: &WickedWirelessMode) -> Self { + match value { + WickedWirelessMode::AdHoc => model::WirelessMode::AdHoc, + WickedWirelessMode::Infrastructure => model::WirelessMode::Infra, + WickedWirelessMode::AP => model::WirelessMode::AP, + } + } +} + +#[derive(Debug, PartialEq, Serialize, Deserialize)] +pub struct WpaPsk { + pub passphrase: String, +} + +#[derive(Debug, PartialEq, Serialize, Deserialize)] +pub struct Wep { + #[serde(rename = "auth-algo")] + pub auth_algo: String, + #[serde(rename = "default-key")] + pub default_key: u32, + pub key: Vec, +} + +fn unwrap_wireless_networks<'de, D>(deserializer: D) -> Result>, D::Error> +where + D: Deserializer<'de>, +{ + #[derive(Debug, PartialEq, Default, Serialize, Deserialize)] + struct Networks { + network: Vec, + } + Ok(Some(Networks::deserialize(deserializer)?.network)) +} + +fn wireless_security_protocol( + wicked_value: &[String], +) -> Result { + if wicked_value.contains(&"wpa-psk".to_string()) + || wicked_value.contains(&"wpa-psk-sha256".to_string()) + { + Ok(model::SecurityProtocol::WPA2) + } else if wicked_value.contains(&"sae".to_string()) { + Ok(model::SecurityProtocol::WPA3Personal) + } else if wicked_value.contains(&"wpa-eap".to_string()) + || wicked_value.contains(&"wpa-eap-sha256".to_string()) + { + Ok(model::SecurityProtocol::WPA2Enterprise) + } else if wicked_value.contains(&"owe".to_string()) { + Ok(model::SecurityProtocol::OWE) + } else if wicked_value.contains(&"wpa-eap-suite-b-192".to_string()) { + Ok(model::SecurityProtocol::WPA3Only) + } else if wicked_value.contains(&"none".to_string()) { + Ok(model::SecurityProtocol::WEP) + } else { + Err(anyhow!("Unrecognized key-management protocol")) + } +} + +impl TryFrom<&Network> for model::ConnectionConfig { + type Error = anyhow::Error; + fn try_from(network: &Network) -> Result { + let settings = MIGRATION_SETTINGS.get().unwrap(); + let mut config = model::WirelessConfig { + ssid: SSID(network.essid.as_bytes().to_vec()), + ..Default::default() + }; + + if network.key_management.len() > 1 && settings.continue_migration { + log::warn!("Migration of multiple key-management algorithms isn't supported") + } else if network.key_management.len() > 1 { + return Err(anyhow!( + "Migration of multiple key-management algorithms isn't supported" + )); + } + config.security = wireless_security_protocol(&network.key_management)?; + + if let Some(wpa_psk) = &network.wpa_psk { + config.password = Some(wpa_psk.passphrase.clone()) + } + if let Some(channel) = network.channel { + config.channel = Some(channel); + if channel <= 14 { + config.band = Some("bg".try_into().unwrap()); + } else { + config.band = Some("a".try_into().unwrap()); + } + log::warn!( + "NetworkManager requires setting a band for wireless when a channel is set. The band has been set to \"{}\". This may in certain regions be incorrect.", + config.band.unwrap() + ); + } + if let Some(access_point) = &network.access_point { + config.bssid = Some(MacAddr6::from_str(access_point)?); + } + + if let Some(wep) = &network.wep { + // filter out `s:`, `h:`, `:`, and `-` of wep keys + let keys: Vec = wep + .key + .clone() + .into_iter() + .map(|mut x| { + x = x.replace("s:", ""); + x = x.replace("h:", ""); + x = x.replace(':', ""); + x.replace('-', "") + }) + .collect(); + let wep_security = WepSecurity { + auth_alg: WepAuthAlg::try_from(wep.auth_algo.as_str())?, + wep_key_type: WepKeyType::Key, + keys, + wep_key_index: wep.default_key, + }; + config.wep_security = Some(wep_security); + } + + config.mode = (&network.mode).into(); + Ok(model::ConnectionConfig::Wireless(config)) + } +} diff --git a/rust/migrate-wicked/tests/wireless/system-connections/wlan0-0.nmconnection b/rust/migrate-wicked/tests/wireless/system-connections/wlan0-0.nmconnection new file mode 100644 index 0000000000..555e655e16 --- /dev/null +++ b/rust/migrate-wicked/tests/wireless/system-connections/wlan0-0.nmconnection @@ -0,0 +1,27 @@ +[connection] +id=wlan0-0 +uuid=1089cc84-f99f-4bb8-8a0d-fafde6544e85 +type=wifi +interface-name=wlan0 + +[wifi] +band=a +bssid=12:34:56:78:9A:BC +channel=100 +mode=adhoc +ssid=example_ssid + +[wifi-security] +key-mgmt=wpa-psk +psk=example_passwd + +[match] + +[ipv4] +method=disabled + +[ipv6] +addr-gen-mode=default +method=disabled + +[proxy] diff --git a/rust/migrate-wicked/tests/wireless/system-connections/wlan0-1.nmconnection b/rust/migrate-wicked/tests/wireless/system-connections/wlan0-1.nmconnection new file mode 100644 index 0000000000..3ce81da933 --- /dev/null +++ b/rust/migrate-wicked/tests/wireless/system-connections/wlan0-1.nmconnection @@ -0,0 +1,24 @@ +[connection] +id=wlan0-1 +uuid=b0c56f00-cf58-4c6c-b6dd-edf97a612063 +type=wifi +interface-name=wlan0 + +[wifi] +mode=adhoc +ssid=example_ssid2 + +[wifi-security] +key-mgmt=wpa-psk +psk=example_passwd2 + +[match] + +[ipv4] +method=disabled + +[ipv6] +addr-gen-mode=default +method=disabled + +[proxy] diff --git a/rust/migrate-wicked/tests/wireless/system-connections/wlan1.nmconnection b/rust/migrate-wicked/tests/wireless/system-connections/wlan1.nmconnection new file mode 100644 index 0000000000..a42db83883 --- /dev/null +++ b/rust/migrate-wicked/tests/wireless/system-connections/wlan1.nmconnection @@ -0,0 +1,29 @@ +[connection] +id=wlan1 +uuid=3bc52d5e-4095-4e1a-9976-d46f766bb627 +type=wifi +interface-name=wlan1 + +[wifi] +bssid=12:34:56:78:9A:BC +mode=infrastructure +ssid=test + +[wifi-security] +auth-alg=shared +key-mgmt=none +wep-key-type=1 +wep-key0=hello +wep-key1=5b73215e232f4c577c5073455d +wep-tx-keyidx=1 + +[match] + +[ipv4] +method=disabled + +[ipv6] +addr-gen-mode=default +method=disabled + +[proxy] diff --git a/rust/migrate-wicked/tests/wireless/wicked_xml/wireless.xml b/rust/migrate-wicked/tests/wireless/wicked_xml/wireless.xml new file mode 100644 index 0000000000..362b03682c --- /dev/null +++ b/rust/migrate-wicked/tests/wireless/wicked_xml/wireless.xml @@ -0,0 +1,77 @@ + + wlan0 + + manual + + + + 1 + + + example_ssid + 100 + 12:34:56:78:9A:BC + true + ad-hoc + wpa-psk,wpa-psk-sha256,sae + + example_passwd + CCMP + CCMP + optional + + + + example_ssid2 + true + ad-hoc + wpa-psk,wpa-psk-sha256,sae + + example_passwd2 + CCMP + CCMP + optional + + + + + + + false + + + false + + + + wlan1 + + manual + + + + 1 + + + test + true + infrastructure + 12:34:56:78:9a:bc + none + + shared + 1 + s:hello + 5b73215e232f4c577c5073455d + + + + + + + false + + + false + +