Skip to content

Commit

Permalink
Merge pull request #893 from jcronenberg/general-mac-address
Browse files Browse the repository at this point in the history
Add mac assigning to network base connection
  • Loading branch information
imobachgs committed Dec 5, 2023
2 parents 2c46c58 + 4ec3314 commit 52dc7a3
Show file tree
Hide file tree
Showing 11 changed files with 185 additions and 5 deletions.
7 changes: 7 additions & 0 deletions rust/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions rust/agama-dbus-server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,4 @@ tokio-stream = "0.1.14"
gettext-rs = { version = "0.7.0", features = ["gettext-system"] }
regex = "1.10.2"
once_cell = "1.18.0"
macaddr = "1.0"
19 changes: 17 additions & 2 deletions rust/agama-dbus-server/src/network/dbus/interfaces.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@ use super::ObjectsRegistry;
use crate::network::{
action::Action,
error::NetworkStateError,
model::{Connection as NetworkConnection, Device as NetworkDevice, WirelessConnection},
model::{
Connection as NetworkConnection, Device as NetworkDevice, MacAddress, WirelessConnection,
},
};

use agama_lib::network::types::SSID;
use std::sync::Arc;
use std::{str::FromStr, sync::Arc};
use tokio::sync::mpsc::UnboundedSender;
use tokio::sync::{MappedMutexGuard, Mutex, MutexGuard};
use zbus::{
Expand Down Expand Up @@ -238,6 +240,19 @@ impl Connection {
connection.set_interface(name);
self.update_connection(connection).await
}

/// Custom mac-address
#[dbus_interface(property)]
pub async fn mac_address(&self) -> String {
self.get_connection().await.mac_address()
}

#[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.set_mac_address(MacAddress::from_str(mac_address)?);
self.update_connection(connection).await
}
}

/// D-Bus interface for Match settings
Expand Down
66 changes: 66 additions & 0 deletions rust/agama-dbus-server/src/network/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -310,12 +310,25 @@ impl Connection {
pub fn is_loopback(&self) -> bool {
matches!(self, Connection::Loopback(_))
}

pub fn is_ethernet(&self) -> bool {
matches!(self, Connection::Loopback(_)) || matches!(self, Connection::Ethernet(_))
}

pub fn mac_address(&self) -> String {
self.base().mac_address.to_string()
}

pub fn set_mac_address(&mut self, mac_address: MacAddress) {
self.base_mut().mac_address = mac_address;
}
}

#[derive(Debug, Default, Clone)]
pub struct BaseConnection {
pub id: String,
pub uuid: Uuid,
pub mac_address: MacAddress,
pub ip_config: IpConfig,
pub status: Status,
pub interface: String,
Expand All @@ -328,6 +341,59 @@ impl PartialEq for BaseConnection {
}
}

#[derive(Debug, Error)]
#[error("Invalid MAC address: {0}")]
pub struct InvalidMacAddress(String);

#[derive(Debug, Default, Clone)]
pub enum MacAddress {
MacAddress(macaddr::MacAddr6),
Preserve,
Permanent,
Random,
Stable,
#[default]
Unset,
}

impl FromStr for MacAddress {
type Err = InvalidMacAddress;

fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"preserve" => Ok(Self::Preserve),
"permanent" => Ok(Self::Permanent),
"random" => Ok(Self::Random),
"stable" => Ok(Self::Stable),
"" => Ok(Self::Unset),
_ => Ok(Self::MacAddress(match macaddr::MacAddr6::from_str(s) {
Ok(mac) => mac,
Err(e) => return Err(InvalidMacAddress(e.to_string())),
})),
}
}
}

impl fmt::Display for MacAddress {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let output = match &self {
Self::MacAddress(mac) => mac.to_string(),
Self::Preserve => "preserve".to_string(),
Self::Permanent => "permanent".to_string(),
Self::Random => "random".to_string(),
Self::Stable => "stable".to_string(),
Self::Unset => "".to_string(),
};
write!(f, "{}", output)
}
}

impl From<InvalidMacAddress> for zbus::fdo::Error {
fn from(value: InvalidMacAddress) -> Self {
zbus::fdo::Error::Failed(value.to_string())
}
}

#[derive(Debug, Default, Clone, Copy, PartialEq)]
pub enum Status {
#[default]
Expand Down
71 changes: 68 additions & 3 deletions rust/agama-dbus-server/src/network/nm/dbus.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,12 @@ pub fn connection_to_dbus(conn: &Connection) -> NestedHash {
result.insert("ipv6", ip_config_to_ipv6_dbus(conn.ip_config()));
result.insert("match", match_config_to_dbus(conn.match_config()));

if let Connection::Wireless(wireless) = conn {
connection_dbus.insert("type", "802-11-wireless".into());
if conn.is_ethernet() {
let ethernet_config =
HashMap::from([("assigned-mac-address", Value::new(conn.mac_address()))]);
result.insert(ETHERNET_KEY, ethernet_config);
} else if let Connection::Wireless(wireless) = conn {
connection_dbus.insert("type", WIRELESS_KEY.into());
let wireless_dbus = wireless_config_to_dbus(wireless);
for (k, v) in wireless_dbus {
result.insert(k, v);
Expand Down Expand Up @@ -227,6 +231,10 @@ fn wireless_config_to_dbus(conn: &WirelessConnection) -> NestedHash {
let wireless: HashMap<&str, zvariant::Value> = HashMap::from([
("mode", Value::new(config.mode.to_string())),
("ssid", Value::new(config.ssid.to_vec())),
(
"assigned-mac-address",
Value::new(conn.base.mac_address.to_string()),
),
]);

let mut security: HashMap<&str, zvariant::Value> =
Expand Down Expand Up @@ -300,11 +308,31 @@ fn base_connection_from_dbus(conn: &OwnedNestedHash) -> Option<BaseConnection> {
base_connection.match_config = match_config_from_dbus(match_config)?;
}

if let Some(ethernet_config) = conn.get(ETHERNET_KEY) {
base_connection.mac_address = mac_address_from_dbus(ethernet_config)?;
} else if let Some(wireless_config) = conn.get(WIRELESS_KEY) {
base_connection.mac_address = mac_address_from_dbus(wireless_config)?;
}

base_connection.ip_config = ip_config_from_dbus(&conn)?;

Some(base_connection)
}

fn mac_address_from_dbus(config: &HashMap<String, OwnedValue>) -> Option<MacAddress> {
if let Some(mac_address) = config.get("assigned-mac-address") {
match MacAddress::from_str(mac_address.downcast_ref::<str>()?) {
Ok(mac) => Some(mac),
Err(e) => {
log::warn!("Couldn't parse MAC: {}", e);
None
}
}
} else {
Some(MacAddress::Unset)
}
}

fn match_config_from_dbus(
match_config: &HashMap<String, zvariant::OwnedValue>,
) -> Option<MatchConfig> {
Expand Down Expand Up @@ -594,6 +622,8 @@ mod test {
let match_config = connection.match_config();
assert_eq!(match_config.kernel, vec!["pci-0000:00:19.0"]);

assert_eq!(connection.mac_address(), "12:34:56:78:9A:BC");

assert_eq!(
ip_config.addresses,
vec![
Expand Down Expand Up @@ -649,6 +679,10 @@ mod test {
"ssid".to_string(),
Value::new("agama".as_bytes()).to_owned(),
),
(
"assigned-mac-address".to_string(),
Value::new("13:45:67:89:AB:CD").to_owned(),
),
]);

let security_section =
Expand All @@ -661,6 +695,7 @@ mod test {
]);

let connection = connection_from_dbus(dbus_conn).unwrap();
assert_eq!(connection.mac_address(), "13:45:67:89:AB:CD".to_string());
assert!(matches!(connection, Connection::Wireless(_)));
if let Connection::Wireless(connection) = connection {
assert_eq!(connection.wireless.ssid, SSID(vec![97, 103, 97, 109, 97]));
Expand Down Expand Up @@ -688,6 +723,12 @@ mod test {
let wireless = wireless_dbus.get("802-11-wireless").unwrap();
let mode: &str = wireless.get("mode").unwrap().downcast_ref().unwrap();
assert_eq!(mode, "infrastructure");
let mac_address: &str = wireless
.get("assigned-mac-address")
.unwrap()
.downcast_ref()
.unwrap();
assert_eq!(mac_address, "FD:CB:A9:87:65:43");

let ssid: &zvariant::Array = wireless.get("ssid").unwrap().downcast_ref().unwrap();
let ssid: Vec<u8> = ssid
Expand Down Expand Up @@ -813,19 +854,33 @@ mod test {
Value::new("eth0".to_string()).to_owned(),
),
]);
let ethernet = HashMap::from([(
"assigned-mac-address".to_string(),
Value::new("12:34:56:78:9A:BC".to_string()).to_owned(),
)]);
original.insert("connection".to_string(), connection);
original.insert(ETHERNET_KEY.to_string(), ethernet);

let mut updated = Connection::Ethernet(EthernetConnection::default());
updated.set_interface("");
updated.set_mac_address(MacAddress::Unset);
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);
let ethernet = merged.get(ETHERNET_KEY).unwrap();
assert_eq!(ethernet.get("assigned-mac-address"), Some(&Value::from("")));
}

fn build_ethernet_section_from_dbus() -> HashMap<String, OwnedValue> {
HashMap::from([("auto-negotiate".to_string(), true.into())])
HashMap::from([
("auto-negotiate".to_string(), true.into()),
(
"assigned-mac-address".to_string(),
Value::new("12:34:56:78:9A:BC").to_owned(),
),
])
}

fn build_base_connection() -> BaseConnection {
Expand All @@ -849,9 +904,11 @@ mod test {
}]),
..Default::default()
};
let mac_address = MacAddress::from_str("FD:CB:A9:87:65:43").unwrap();
BaseConnection {
id: "agama".to_string(),
ip_config,
mac_address,
..Default::default()
}
}
Expand All @@ -868,6 +925,14 @@ mod test {
let id: &str = connection_dbus.get("id").unwrap().downcast_ref().unwrap();
assert_eq!(id, "agama");

let ethernet_connection = conn_dbus.get(ETHERNET_KEY).unwrap();
let mac_address: &str = ethernet_connection
.get("assigned-mac-address")
.unwrap()
.downcast_ref()
.unwrap();
assert_eq!(mac_address, "FD:CB:A9:87:65:43");

let ipv4_dbus = conn_dbus.get("ipv4").unwrap();
let gateway4: &str = ipv4_dbus.get("gateway").unwrap().downcast_ref().unwrap();
assert_eq!(gateway4, "192.168.0.1");
Expand Down
2 changes: 2 additions & 0 deletions rust/agama-dbus-server/tests/network.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ async fn test_add_connection() -> Result<(), Box<dyn Error>> {
let addresses: Vec<IpInet> = vec!["192.168.0.2/24".parse()?, "::ffff:c0a8:7ac7/64".parse()?];
let wlan0 = settings::NetworkConnection {
id: "wlan0".to_string(),
mac_address: Some("FD:CB:A9:87:65:43".to_string()),
method4: Some("auto".to_string()),
method6: Some("disabled".to_string()),
addresses: addresses.clone(),
Expand All @@ -80,6 +81,7 @@ async fn test_add_connection() -> Result<(), Box<dyn Error>> {

let conn = conns.first().unwrap();
assert_eq!(conn.id, "wlan0");
assert_eq!(conn.mac_address, Some("FD:CB:A9:87:65:43".to_string()));
assert_eq!(conn.device_type(), DeviceType::Wireless);
assert_eq!(&conn.addresses, &addresses);
let method4 = conn.method4.as_ref().unwrap();
Expand Down
4 changes: 4 additions & 0 deletions rust/agama-lib/share/profile.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@
"description": "The name of the network interface bound to this connection",
"type": "string"
},
"mac-address": {
"description": "Custom mac-address (can also be 'preserve', 'permanent', 'random' or 'stable')",
"type": "string"
},
"method4": {
"description": "IPv4 configuration method (e.g., 'auto')",
"type": "string",
Expand Down
8 changes: 8 additions & 0 deletions rust/agama-lib/src/network/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ impl<'a> NetworkClient<'a> {
"" => None,
value => Some(value.to_string()),
};
let mac_address = match connection_proxy.mac_address().await?.as_str() {
"" => None,
value => Some(value.to_string()),
};

let ip_proxy = IPProxy::builder(&self.connection)
.path(path)?
Expand All @@ -91,6 +95,7 @@ impl<'a> NetworkClient<'a> {
addresses,
nameservers,
interface,
mac_address,
..Default::default()
})
}
Expand Down Expand Up @@ -189,6 +194,9 @@ impl<'a> NetworkClient<'a> {
let interface = conn.interface.as_deref().unwrap_or("");
proxy.set_interface(interface).await?;

let mac_address = conn.mac_address.as_deref().unwrap_or("");
proxy.set_mac_address(mac_address).await?;

self.update_ip_settings(path, conn).await?;

if let Some(ref wireless) = conn.wireless {
Expand Down
4 changes: 4 additions & 0 deletions rust/agama-lib/src/network/proxies.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,10 @@ trait Connection {
fn interface(&self) -> zbus::Result<String>;
#[dbus_proxy(property)]
fn set_interface(&self, interface: &str) -> zbus::Result<()>;
#[dbus_proxy(property)]
fn mac_address(&self) -> zbus::Result<String>;
#[dbus_proxy(property)]
fn set_mac_address(&self, mac_address: &str) -> zbus::Result<()>;
}

#[dbus_proxy(
Expand Down
2 changes: 2 additions & 0 deletions rust/agama-lib/src/network/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ pub struct NetworkConnection {
pub interface: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub match_settings: Option<MatchSettings>,
#[serde(rename = "mac-address", skip_serializing_if = "Option::is_none")]
pub mac_address: Option<String>,
}

impl NetworkConnection {
Expand Down
6 changes: 6 additions & 0 deletions rust/package/agama-cli.changes
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
-------------------------------------------------------------------
Tue Dec 5 11:18:41 UTC 2023 - Jorik Cronenberg <jorik.cronenberg@suse.com>

- Add ability to assign a custom MAC address for network
connections (gh#openSUSE/agama#893)

-------------------------------------------------------------------
Tue Dec 5 09:46:48 UTC 2023 - José Iván López González <jlopez@suse.com>

Expand Down

0 comments on commit 52dc7a3

Please sign in to comment.