Skip to content
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
2 changes: 1 addition & 1 deletion Cargo.lock

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

203 changes: 196 additions & 7 deletions nmrs/src/api/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -534,12 +534,8 @@ pub struct EapOptions {
/// ```rust
/// use nmrs::ConnectionOptions;
///
/// // Basic auto-connect
/// let opts = ConnectionOptions {
/// autoconnect: true,
/// autoconnect_priority: None,
/// autoconnect_retries: None,
/// };
/// // Basic auto-connect (using defaults)
/// let opts = ConnectionOptions::default();
///
/// // High-priority connection with retry limit
/// let opts_priority = ConnectionOptions {
Expand All @@ -565,6 +561,22 @@ pub struct ConnectionOptions {
pub autoconnect_retries: Option<i32>,
}

impl Default for ConnectionOptions {
/// Returns the default connection options.
///
/// Defaults:
/// - `autoconnect`: `true`
/// - `autoconnect_priority`: `None` (uses NetworkManager's default of 0)
/// - `autoconnect_retries`: `None` (unlimited retries)
fn default() -> Self {
Self {
autoconnect: true,
autoconnect_priority: None,
autoconnect_retries: None,
}
}
}

/// Wi-Fi connection security types.
///
/// Represents the authentication method for connecting to a WiFi network.
Expand Down Expand Up @@ -835,6 +847,8 @@ pub struct VpnConnectionInfo {
/// NetworkManager device types.
///
/// Represents the type of network hardware managed by NetworkManager.
/// This enum uses a registry-based system to support adding new device
/// types without breaking the API.
#[derive(Debug, Clone, PartialEq)]
pub enum DeviceType {
/// Wired Ethernet device.
Expand All @@ -846,9 +860,83 @@ pub enum DeviceType {
/// Loopback device (localhost).
Loopback,
/// Unknown or unsupported device type with raw code.
///
/// Use the methods on `DeviceType` to query capabilities of unknown device types,
/// which will consult the internal device type registry.
Other(u32),
}

impl DeviceType {
/// Returns whether this device type supports network scanning.
///
/// Currently only WiFi and WiFi P2P devices support scanning.
/// For unknown device types, consults the internal device type registry.
pub fn supports_scanning(&self) -> bool {
match self {
Self::Wifi | Self::WifiP2P => true,
Self::Other(code) => crate::types::device_type_registry::supports_scanning(*code),
_ => false,
}
}

/// Returns whether this device type requires a specific object (like an access point).
///
/// WiFi devices require an access point to connect to, while Ethernet can connect
/// without a specific target.
/// For unknown device types, consults the internal device type registry.
pub fn requires_specific_object(&self) -> bool {
match self {
Self::Wifi | Self::WifiP2P => true,
Self::Other(code) => {
crate::types::device_type_registry::requires_specific_object(*code)
}
_ => false,
}
}

/// Returns whether this device type has a global enabled/disabled state.
///
/// WiFi has a global radio killswitch that can enable/disable all WiFi devices.
/// For unknown device types, consults the internal device type registry.
pub fn has_global_enabled_state(&self) -> bool {
match self {
Self::Wifi => true,
Self::Other(code) => {
crate::types::device_type_registry::has_global_enabled_state(*code)
}
_ => false,
}
}

/// Returns the NetworkManager connection type string for this device.
///
/// This is used when creating connection profiles for this device type.
/// For unknown device types, consults the internal device type registry.
pub fn connection_type_str(&self) -> &'static str {
match self {
Self::Ethernet => "802-3-ethernet",
Self::Wifi => "802-11-wireless",
Self::WifiP2P => "wifi-p2p",
Self::Loopback => "loopback",
Self::Other(code) => {
crate::types::device_type_registry::connection_type_for_code(*code)
.unwrap_or("generic")
}
}
}

/// Returns the raw NetworkManager type code for this device.
pub fn to_code(&self) -> u32 {
match self {
Self::Ethernet => 1,
Self::Wifi => 2,
Self::WifiP2P => 30,
Self::Loopback => 32,
Self::Other(code) => *code,
}
}
}

/// NetworkManager device states.
///
/// Represents the current operational state of a network device.
Expand Down Expand Up @@ -1218,7 +1306,13 @@ impl Display for DeviceType {
DeviceType::Wifi => write!(f, "Wi-Fi"),
DeviceType::WifiP2P => write!(f, "Wi-Fi P2P"),
DeviceType::Loopback => write!(f, "Loopback"),
DeviceType::Other(v) => write!(f, "Other({v})"),
DeviceType::Other(v) => {
write!(
f,
"{}",
crate::types::device_type_registry::display_name_for_code(*v)
)
}
}
}
}
Expand Down Expand Up @@ -1289,6 +1383,15 @@ mod tests {
assert_eq!(DeviceType::from(0), DeviceType::Other(0));
}

#[test]
fn device_type_from_u32_registry_types() {
assert_eq!(DeviceType::from(11), DeviceType::Other(11));
assert_eq!(DeviceType::from(12), DeviceType::Other(12));
assert_eq!(DeviceType::from(13), DeviceType::Other(13));
assert_eq!(DeviceType::from(16), DeviceType::Other(16));
assert_eq!(DeviceType::from(29), DeviceType::Other(29));
}

#[test]
fn device_type_display() {
assert_eq!(format!("{}", DeviceType::Ethernet), "Ethernet");
Expand All @@ -1298,6 +1401,92 @@ mod tests {
assert_eq!(format!("{}", DeviceType::Other(42)), "Other(42)");
}

#[test]
fn device_type_display_registry() {
assert_eq!(format!("{}", DeviceType::Other(13)), "Bridge");
assert_eq!(format!("{}", DeviceType::Other(12)), "Bond");
assert_eq!(format!("{}", DeviceType::Other(11)), "VLAN");
assert_eq!(format!("{}", DeviceType::Other(16)), "TUN");
assert_eq!(format!("{}", DeviceType::Other(29)), "WireGuard");
}

#[test]
fn device_type_supports_scanning() {
assert!(DeviceType::Wifi.supports_scanning());
assert!(DeviceType::WifiP2P.supports_scanning());
assert!(!DeviceType::Ethernet.supports_scanning());
assert!(!DeviceType::Loopback.supports_scanning());
}

#[test]
fn device_type_supports_scanning_registry() {
assert!(DeviceType::Other(30).supports_scanning());
assert!(!DeviceType::Other(13).supports_scanning());
assert!(!DeviceType::Other(29).supports_scanning());
}

#[test]
fn device_type_requires_specific_object() {
assert!(DeviceType::Wifi.requires_specific_object());
assert!(DeviceType::WifiP2P.requires_specific_object());
assert!(!DeviceType::Ethernet.requires_specific_object());
assert!(!DeviceType::Loopback.requires_specific_object());
}

#[test]
fn device_type_requires_specific_object_registry() {
assert!(DeviceType::Other(2).requires_specific_object());
assert!(!DeviceType::Other(1).requires_specific_object());
assert!(!DeviceType::Other(29).requires_specific_object());
}

#[test]
fn device_type_has_global_enabled_state() {
assert!(DeviceType::Wifi.has_global_enabled_state());
assert!(!DeviceType::Ethernet.has_global_enabled_state());
assert!(!DeviceType::WifiP2P.has_global_enabled_state());
}

#[test]
fn device_type_has_global_enabled_state_registry() {
assert!(DeviceType::Other(2).has_global_enabled_state());
assert!(!DeviceType::Other(1).has_global_enabled_state());
}

#[test]
fn device_type_connection_type_str() {
assert_eq!(DeviceType::Ethernet.connection_type_str(), "802-3-ethernet");
assert_eq!(DeviceType::Wifi.connection_type_str(), "802-11-wireless");
assert_eq!(DeviceType::WifiP2P.connection_type_str(), "wifi-p2p");
assert_eq!(DeviceType::Loopback.connection_type_str(), "loopback");
}

#[test]
fn device_type_connection_type_str_registry() {
assert_eq!(DeviceType::Other(13).connection_type_str(), "bridge");
assert_eq!(DeviceType::Other(12).connection_type_str(), "bond");
assert_eq!(DeviceType::Other(11).connection_type_str(), "vlan");
assert_eq!(DeviceType::Other(29).connection_type_str(), "wireguard");
}

#[test]
fn device_type_to_code() {
assert_eq!(DeviceType::Ethernet.to_code(), 1);
assert_eq!(DeviceType::Wifi.to_code(), 2);
assert_eq!(DeviceType::WifiP2P.to_code(), 30);
assert_eq!(DeviceType::Loopback.to_code(), 32);
assert_eq!(DeviceType::Other(999).to_code(), 999);
}

#[test]
fn device_type_to_code_registry() {
assert_eq!(DeviceType::Other(11).to_code(), 11);
assert_eq!(DeviceType::Other(12).to_code(), 12);
assert_eq!(DeviceType::Other(13).to_code(), 13);
assert_eq!(DeviceType::Other(16).to_code(), 16);
assert_eq!(DeviceType::Other(29).to_code(), 29);
}

#[test]
fn device_state_from_u32_all_variants() {
assert_eq!(DeviceState::from(10), DeviceState::Unmanaged);
Expand Down
23 changes: 4 additions & 19 deletions nmrs/src/core/connection_settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use std::collections::HashMap;
use zbus::Connection;
use zvariant::{OwnedObjectPath, Value};

use crate::util::utils::nm_proxy;
use crate::util::utils::{connection_settings_proxy, settings_proxy};
use crate::Result;

/// Finds the D-Bus path of a saved connection by SSID.
Expand All @@ -23,23 +23,13 @@ pub(crate) async fn get_saved_connection_path(
conn: &Connection,
ssid: &str,
) -> Result<Option<OwnedObjectPath>> {
let settings = nm_proxy(
conn,
"/org/freedesktop/NetworkManager/Settings",
"org.freedesktop.NetworkManager.Settings",
)
.await?;
let settings = settings_proxy(conn).await?;

let reply = settings.call_method("ListConnections", &()).await?;
let conns: Vec<OwnedObjectPath> = reply.body().deserialize()?;

for cpath in conns {
let cproxy = nm_proxy(
conn,
cpath.as_str(),
"org.freedesktop.NetworkManager.Settings.Connection",
)
.await?;
let cproxy = connection_settings_proxy(conn, cpath.clone()).await?;

let msg = cproxy.call_method("GetSettings", &()).await?;
let body = msg.body();
Expand Down Expand Up @@ -69,12 +59,7 @@ pub(crate) async fn has_saved_connection(conn: &Connection, ssid: &str) -> Resul
/// Calls the Delete method on the connection settings object.
/// This permanently removes the saved connection from NetworkManager.
pub(crate) async fn delete_connection(conn: &Connection, conn_path: OwnedObjectPath) -> Result<()> {
let cproxy = nm_proxy(
conn,
conn_path.clone(),
"org.freedesktop.NetworkManager.Settings.Connection",
)
.await?;
let cproxy = connection_settings_proxy(conn, conn_path.clone()).await?;

cproxy.call_method("Delete", &()).await?;
debug!("Deleted connection: {}", conn_path.as_str());
Expand Down
13 changes: 3 additions & 10 deletions nmrs/src/core/vpn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ use crate::api::models::{
use crate::builders::build_wireguard_connection;
use crate::core::state_wait::wait_for_connection_activation;
use crate::dbus::{NMActiveConnectionProxy, NMProxy};
use crate::util::utils::{extract_connection_state_reason, nm_proxy};
use crate::util::utils::{extract_connection_state_reason, nm_proxy, settings_proxy};
use crate::Result;

/// Connects to a WireGuard connection.
Expand Down Expand Up @@ -61,14 +61,7 @@ pub(crate) async fn connect_vpn(conn: &Connection, creds: VpnCredentials) -> Res

let settings = build_wireguard_connection(&creds, &opts)?;

// Use Settings API to add connection first, then activate separately
// This avoids NetworkManager's device validation when using add_and_activate_connection
let settings_api = nm_proxy(
conn,
"/org/freedesktop/NetworkManager/Settings",
"org.freedesktop.NetworkManager.Settings",
)
.await?;
let settings_api = settings_proxy(conn).await?;

debug!("Adding connection via Settings API");
let add_reply = settings_api
Expand Down Expand Up @@ -718,7 +711,7 @@ pub(crate) async fn get_vpn_info(conn: &Connection, name: &str) -> Result<VpnCon
_ => None,
})?;
let prefix = addr_map.get("prefix").and_then(|v| match v {
zvariant::Value::U32(p) => Some(*p),
zvariant::Value::U32(p) => Some(p),
_ => None,
})?;
Some(format!("{}/{}", address, prefix))
Expand Down
Loading
Loading