Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add mac assigning to network base connection #893

Merged
merged 6 commits into from
Dec 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 <[email protected]>

- 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 <[email protected]>

Expand Down