Skip to content

Commit

Permalink
Extend NetworkState with wireless settings
Browse files Browse the repository at this point in the history
  • Loading branch information
imobachgs committed May 4, 2023
1 parent 8aafb06 commit 054ac78
Show file tree
Hide file tree
Showing 5 changed files with 172 additions and 46 deletions.
2 changes: 2 additions & 0 deletions rust/agama-lib/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ use zbus;
pub enum ServiceError {
#[error("D-Bus service error: {0}")]
DBus(#[from] zbus::Error),
#[error("Unexpected or missing data")]
MissingData,
// it's fine to say only "Error" because the original
// specific error will be printed too
#[error("Error: {0}")]
Expand Down
37 changes: 23 additions & 14 deletions rust/agama-lib/src/network/nm/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use crate::{
error::ServiceError,
network::nm::proxies::{DeviceProxy, NetworkManagerProxy},
};
use std::collections::HashMap;
use zbus::zvariant;
use zbus::Connection;

Expand Down Expand Up @@ -61,36 +62,44 @@ impl<'a> NetworkManagerClient<'a> {
.await?;

let conn = proxy.get_applied_connection(0).await?;
let mut result = NmConnection::default();
self.connection_from_dbus(conn.0)
.ok_or(ServiceError::MissingData)
}

fn connection_from_dbus(
&self,
conn: HashMap<String, HashMap<String, zvariant::OwnedValue>>,
) -> Option<NmConnection> {
let mut nm_connection = NmConnection::default();

if let Some(connection) = conn.0.get("connection") {
let id: &str = connection.get("id").unwrap().downcast_ref().unwrap();
result.id = id.to_string();
if let Some(connection) = conn.get("connection") {
let id: &str = connection.get("id")?.downcast_ref()?;
nm_connection.id = id.to_string();
}

if let Some(wireless) = conn.0.get("802-11-wireless") {
let mode: &str = wireless.get("mode").unwrap().downcast_ref().unwrap();
let ssid = wireless.get("ssid").unwrap();
let ssid: &zvariant::Array = ssid.downcast_ref().unwrap();
if let Some(wireless) = conn.get("802-11-wireless") {
let mode: &str = wireless.get("mode")?.downcast_ref()?;
let ssid = wireless.get("ssid")?;
let ssid: &zvariant::Array = ssid.downcast_ref()?;
let ssid: Vec<u8> = ssid
.get()
.iter()
.map(|u| *u.downcast_ref::<u8>().unwrap())
.collect();
let mut wireless_settings = NmWireless {
mode: NmWirelessMode(mode.to_string()),
mode: mode.into(),
ssid,
..Default::default()
};

if let Some(security) = conn.0.get("802-11-wireless-security") {
let protocol: &str = security.get("key-mgmt").unwrap().downcast_ref().unwrap();
wireless_settings.key_mgmt = NmKeyManagement(protocol.to_string());
if let Some(security) = conn.get("802-11-wireless-security") {
let key_mgmt: &str = security.get("key-mgmt")?.downcast_ref()?;
wireless_settings.key_mgmt = key_mgmt.into();
}

result.wireless = Some(wireless_settings);
nm_connection.wireless = Some(wireless_settings);
}

Ok(result)
Some(nm_connection)
}
}
22 changes: 12 additions & 10 deletions rust/agama-lib/src/network/nm/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
//!
//! This are meant to be used internally, so we omit everything it is not useful for us.

use std::convert::TryFrom;

/// NetworkManager device
#[derive(Debug, Default)]
pub struct NmDevice {
Expand All @@ -30,8 +28,6 @@ pub struct NmConnection {
pub name: String,
/// Wireless settings
pub wireless: Option<NmWireless>,
/// Wireless security settings
pub wireless_security: Option<NmWirelessSecurity>,
}

#[derive(Debug, Default, PartialEq)]
Expand All @@ -44,12 +40,6 @@ pub struct NmWireless {
pub key_mgmt: NmKeyManagement,
}

/// NetworkManager wireless security settings
#[derive(Debug, PartialEq)]
pub struct NmWirelessSecurity {
pub auth_alg: String,
}

/// NetworkManager wireless mode
///
/// Using the newtype pattern around an String is enough. For proper support, we might replace this
Expand All @@ -63,6 +53,12 @@ impl Default for NmWirelessMode {
}
}

impl From<&str> for NmWirelessMode {
fn from(value: &str) -> Self {
Self(value.to_string())
}
}

impl NmWirelessMode {
pub fn as_str(&self) -> &str {
&self.0.as_str()
Expand Down Expand Up @@ -96,6 +92,12 @@ impl Default for NmKeyManagement {
}
}

impl From<&str> for NmKeyManagement {
fn from(value: &str) -> Self {
Self(value.to_string())
}
}

impl NmKeyManagement {
pub fn as_str(&self) -> &str {
&self.0.as_str()
Expand Down
152 changes: 132 additions & 20 deletions rust/agama-lib/src/network/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
//!
//! This module contains the network's data model. It is based on
//! [nmstate](https://crates.io/crates/nmstate), adding some features that Agama need.
use super::nm::{NetworkManagerClient, NmConnection, NmKeyManagement, NmWirelessMode};
use nmstate;
use std::error::Error;
use thiserror;
Expand All @@ -22,31 +23,59 @@ impl From<NetworkStateError> for zbus::fdo::Error {
}
}

#[derive(Default)]
struct NetworkStateBuilder {}

impl NetworkStateBuilder {
async fn build(&self) -> Result<NetworkState, Box<dyn Error>> {
let mut net_state = nmstate::NetworkState::new();
net_state.retrieve()?;

let nm = NetworkManagerClient::from_system().await.unwrap();
let devs = nm.devices().await?;

let mut devices: Vec<NetworkDevice> = vec![];
for dev in devs {
dbg!(&dev.iface);
let nmstate_iface = net_state
.interfaces
.get_iface(&dev.iface, nmstate::InterfaceType::Unknown);

let Some(iface) = nmstate_iface else {
continue;
};

if dev.is_wireless() {
let conn = nm.applied_connection(&dev.path).await?;
devices.push(NetworkDevice::Wireless(
iface.clone(),
WirelessSettings::from_nm_connection(conn),
))
} else {
devices.push(NetworkDevice::Generic(iface.clone()))
}
}

Ok(NetworkState {
state: net_state,
devices,
})
}
}

/// Network configuration, including interfaces, DNS settings, etc.
///
/// It is a wrapper around [nmstate::NetworkState] that adds support for missing stuff in nmstate.
/// It also allows extending the API to fit Agama's use case better.
#[derive(Debug)]
#[derive(Debug, Default)]
pub struct NetworkState {
state: nmstate::NetworkState,
devices: Vec<NetworkDevice>,
}

impl NetworkState {
pub fn new(state: nmstate::NetworkState) -> Self {
let devices = state
.interfaces
.iter()
.map(|i| NetworkDevice::Generic(i.clone()))
.collect();
Self { state, devices }
}

/// Retrieves the network state from the underlying system
pub fn from_system() -> Result<Self, Box<dyn Error>> {
let mut net_state = nmstate::NetworkState::new();
net_state.retrieve()?;
Ok(NetworkState::new(net_state))
pub async fn from_system() -> Result<Self, Box<dyn Error>> {
NetworkStateBuilder::default().build().await
}

pub fn apply(&mut self) -> Result<(), NetworkStateError> {
Expand Down Expand Up @@ -139,14 +168,14 @@ impl NetworkState {
#[derive(Debug)]
pub enum NetworkDevice {
Generic(nmstate::Interface),
Wireless(nmstate::Interface),
Wireless(nmstate::Interface, WirelessSettings),
}

impl NetworkDevice {
pub fn iface(&self) -> &nmstate::Interface {
match self {
Self::Generic(iface) => iface,
Self::Wireless(iface) => iface,
Self::Wireless(iface, _) => iface,
}
}

Expand All @@ -167,6 +196,80 @@ impl NetworkDevice {
}
}

#[derive(Debug, Default, Clone, Copy)]
pub enum WirelessMode {
Unknown,
AdHoc,
#[default]
Infra,
AP,
Mesh,
}

impl From<NmWirelessMode> for WirelessMode {
fn from(value: NmWirelessMode) -> Self {
match value.as_str() {
"infrastructure" => WirelessMode::Infra,
"adhoc" => WirelessMode::AdHoc,
"mesh" => WirelessMode::Mesh,
"ap" => WirelessMode::AP,
_ => WirelessMode::Unknown,
}
}
}

#[derive(Debug, Clone, Copy)]
pub enum SecurityProtocol {
// No encryption or WEP ("none")
WEP,
// Opportunistic Wireless Encryption ("owe")
OWE,
// Dynamic WEP ("ieee8021x")
DynamicWEP,
// WPA2 + WPA3 personal ("wpa-psk")
WPA2,
// WPA3 personal only ("sae")
WPA3Personal,
// WPA2 + WPA3 Enterprise ("wpa-eap")
WPA2Enterprise,
// "wpa-eap-suite-b192"
WPA3Only,
}

impl From<NmKeyManagement> for SecurityProtocol {
fn from(value: NmKeyManagement) -> Self {
match value.as_str() {
"owe" => SecurityProtocol::OWE,
"ieee8021x" => SecurityProtocol::DynamicWEP,
"wpa-psk" => SecurityProtocol::WPA2,
"wpa-eap" => SecurityProtocol::WPA3Personal,
"sae" => SecurityProtocol::WPA2Enterprise,
"wpa-eap-suite-b192" => SecurityProtocol::WPA2Enterprise,
_ => SecurityProtocol::WEP,
}
}
}

#[derive(Debug, Default)]
pub struct WirelessSettings {
pub mode: WirelessMode,
pub ssid: Vec<u8>,
pub password: Option<String>,
pub security: Option<SecurityProtocol>,
}

impl WirelessSettings {
pub fn from_nm_connection(conn: NmConnection) -> Self {
let mut settings = WirelessSettings::default();
if let Some(wireless) = conn.wireless {
settings.mode = wireless.mode.into();
settings.ssid = wireless.ssid;
settings.security = Some(wireless.key_mgmt.into());
}
settings
}
}

#[cfg(test)]
mod tests {
#[test]
Expand All @@ -177,7 +280,10 @@ mod tests {
}"#,
)
.unwrap();
let state = super::NetworkState::new(inner_state);
let state = super::NetworkState {
state: inner_state,
..Default::default()
};
let interfaces = state.interfaces().to_vec();
assert_eq!(interfaces.len(), 1);
let eth0 = interfaces.get(0).unwrap();
Expand All @@ -196,7 +302,10 @@ mod tests {
}"#,
)
.unwrap();
let state = super::NetworkState::new(inner_state);
let state = super::NetworkState {
state: inner_state,
..Default::default()
};
let iface = state.get_iface("eth0").unwrap();
assert_eq!(iface.base_iface().name, "eth0");
assert!(state.get_iface("eth1").is_none());
Expand All @@ -213,7 +322,10 @@ mod tests {
}"#,
)
.unwrap();
let state = super::NetworkState::new(inner_state);
let state = super::NetworkState {
state: inner_state,
..Default::default()
};
let device: &super::NetworkDevice = state.get_device("eth1").unwrap();
assert_eq!(device.name(), "eth1");
}
Expand Down
5 changes: 3 additions & 2 deletions rust/agama-network/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ use async_std;

#[async_std::main]
async fn main() {
let state = NetworkState::from_system().expect("Could not retrieve the network state");

let state = NetworkState::from_system()
.await
.expect("Could not retrieve the network state");
let connection = connection()
.await
.expect("Could not connect to the D-Bus server");
Expand Down

0 comments on commit 054ac78

Please sign in to comment.