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: 2 additions & 0 deletions nmrs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@ All notable changes to the `nmrs` crate will be documented in this file.
- `WifiInterfaceNotFound` and `NotAWifiDevice` error variants
- Saved profile enumeration: `SavedConnection`, `SavedConnectionBrief`, `SettingsSummary`, `SettingsPatch`, `WifiSecuritySummary`, `WifiKeyMgmt`, `VpnSecretFlags`; `list_saved_connections()`, `list_saved_connections_brief()`, `list_saved_connection_ids()`, `get_saved_connection()`, `get_saved_connection_raw()`, `delete_saved_connection()`, `update_saved_connection()`, `reload_saved_connections()`; D-Bus proxies `NMSettingsProxy` / `NMSettingsConnectionProxy`; example `saved_list`
- Connectivity state surface: `ConnectivityState`, `ConnectivityReport`, `connectivity()`, `check_connectivity()`, `connectivity_report()`, `captive_portal_url()`; `ConnectivityCheckDisabled` error variant
- Generic VPN support: `VpnType` now carries protocol-specific metadata for OpenVPN, OpenConnect, strongSwan, PPTP, L2TP, and a `Generic` catch-all; `VpnKind` (Plugin vs WireGuard); `VpnConnection` enriched with `uuid`, `active`, `user_name`, `password_flags`, `service_type`; `connect_vpn_by_uuid()`, `connect_vpn_by_id()`, `disconnect_vpn_by_uuid()`, `active_vpn_connections()`

### Changed
- `VpnType` is now a data-carrying enum; the old tag enum is renamed to `VpnKind`. `VpnConfig::vpn_type()` renamed to `vpn_kind()`. `VpnConnectionInfo.vpn_type` renamed to `vpn_kind`.
-`list_saved_connections()` now returns `Vec<SavedConnection>` (full decode + summaries). Use `list_saved_connection_ids()` for the previous `Vec<String>` behavior (connection `id` names only).
-`connect`, `connect_to_bssid`, `disconnect`, `scan_networks`, and `list_networks` now take an `interface: Option<&str>` parameter. Pass `None` to preserve previous behavior, or `Some("wlan1")` to scope to a specific Wi-Fi interface. For an ergonomic per-interface API, use `nm.wifi("wlan1")` to obtain a `WifiScope`.
-`set_wifi_enabled` now requires an `interface: &str` argument and toggles only that radio (via `Device.Autoconnect` + `Device.Disconnect()`). For the global wireless killswitch use `set_wireless_enabled(bool)`.
Expand Down
4 changes: 4 additions & 0 deletions nmrs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,7 @@ path = "examples/saved_list.rs"
[[example]]
name = "connectivity"
path = "examples/connectivity.rs"

[[example]]
name = "vpn_list"
path = "examples/vpn_list.rs"
2 changes: 1 addition & 1 deletion nmrs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Rust bindings for NetworkManager via D-Bus.
## Features

- **WiFi Management**: Connect to WPA-PSK, WPA-EAP, and open networks
- **VPN Support**: WireGuard VPN connections with full configuration
- **VPN Support**: WireGuard, OpenVPN, OpenConnect, strongSwan, PPTP, L2TP, and generic plugin VPNs with rich enumeration and UUID-based activation
- **Ethernet**: Wired network connection management
- **Network Discovery**: Scan and list available access points with per-BSSID detail and security capabilities
- **Per-Interface Scoping**: Target specific Wi-Fi radios on multi-NIC systems via `nm.wifi("wlan1")` or `Option<&str>` interface arguments
Expand Down
45 changes: 45 additions & 0 deletions nmrs/examples/vpn_list.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
use nmrs::NetworkManager;

#[tokio::main]
async fn main() -> nmrs::Result<()> {
let nm = NetworkManager::new().await?;
let vpns = nm.list_vpn_connections().await?;

println!(
"{:<20} {:<38} {:<16} {:<12} active",
"id", "uuid", "type", "user"
);
println!("{}", "-".repeat(90));

for vpn in &vpns {
let type_label = match &vpn.vpn_type {
nmrs::VpnType::WireGuard { .. } => "wireguard".to_string(),
nmrs::VpnType::OpenVpn {
connection_type, ..
} => {
format!(
"openvpn/{}",
connection_type
.map(|ct| format!("{ct:?}"))
.unwrap_or_default()
)
}
nmrs::VpnType::OpenConnect { .. } => "openconnect".to_string(),
nmrs::VpnType::StrongSwan { .. } => "strongswan".to_string(),
nmrs::VpnType::Pptp { .. } => "pptp".to_string(),
nmrs::VpnType::L2tp { .. } => "l2tp".to_string(),
nmrs::VpnType::Generic { service_type, .. } => service_type.clone(),
_ => "(unknown)".to_string(),
};

let user = vpn.user_name.as_deref().unwrap_or("(n/a)");
let active_icon = if vpn.active { "●" } else { "○" };

println!(
"{:<20} {:<38} {:<16} {:<12} {}",
vpn.id, vpn.uuid, type_label, user, active_icon
);
}

Ok(())
}
4 changes: 2 additions & 2 deletions nmrs/src/api/builders/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
//!
//! ```ignore
//! use nmrs::builders::{build_wifi_connection, build_wireguard_connection, build_ethernet_connection};
//! use nmrs::{WifiSecurity, ConnectionOptions, VpnCredentials, VpnType, WireGuardPeer};
//! use nmrs::{WifiSecurity, ConnectionOptions, VpnCredentials, VpnKind, WireGuardPeer};
//!
//! let opts = ConnectionOptions {
//! autoconnect: true,
Expand All @@ -45,7 +45,7 @@
//! };
//!
//! let creds = VpnCredentials {
//! vpn_type: VpnType::WireGuard,
//! vpn_type: VpnKind::WireGuard,
//! name: "MyVPN".into(),
//! gateway: "vpn.example.com:51820".into(),
//! private_key: "YBk6X3pP8KjKz7+HFWzVHNqL3qTZq8hX9VxFQJ4zVmM=".into(),
Expand Down
8 changes: 4 additions & 4 deletions nmrs/src/api/builders/vpn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
//!
//! ```rust
//! use nmrs::builders::build_wireguard_connection;
//! use nmrs::{VpnCredentials, VpnType, WireGuardPeer, ConnectionOptions};
//! use nmrs::{VpnCredentials, VpnKind, WireGuardPeer, ConnectionOptions};
//!
//! let peer = WireGuardPeer::new(
//! "HIgo9xNzJMWLKAShlKl6/bUT1VI9Q0SDBXGtLXkPFXc=",
Expand All @@ -50,7 +50,7 @@
//! ).with_persistent_keepalive(25);
//!
//! let creds = VpnCredentials::new(
//! VpnType::WireGuard,
//! VpnKind::WireGuard,
//! "MyVPN",
//! "vpn.example.com:51820",
//! "YBk6X3pP8KjKz7+HFWzVHNqL3qTZq8hX9VxFQJ4zVmM=",
Expand Down Expand Up @@ -398,7 +398,7 @@ pub fn build_openvpn_connection(
mod tests {
use super::*;
use crate::api::models::{
OpenVpnCompression, OpenVpnConfig, OpenVpnProxy, VpnType, WireGuardPeer,
OpenVpnCompression, OpenVpnConfig, OpenVpnProxy, VpnKind, WireGuardPeer,
};

fn create_test_credentials() -> VpnCredentials {
Expand All @@ -410,7 +410,7 @@ mod tests {
.with_persistent_keepalive(25);

VpnCredentials::new(
VpnType::WireGuard,
VpnKind::WireGuard,
"TestVPN",
"vpn.example.com:51820",
"YBk6X3pP8KjKz7+HFWzVHNqL3qTZq8hX9VxFQJ4zVmM=",
Expand Down
8 changes: 8 additions & 0 deletions nmrs/src/api/models/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,14 @@ pub enum ConnectionError {
#[error("no VPN connection found")]
NoVpnConnection,

/// VPN connection not found by UUID or name.
#[error("VPN connection '{0}' not found")]
VpnNotFound(String),

/// Multiple VPN connections share the same display name.
#[error("multiple VPN connections named '{0}', use UUID")]
VpnIdAmbiguous(String),

/// Invalid IP address or CIDR notation
#[error("invalid address: {0}")]
InvalidAddress(String),
Expand Down
6 changes: 3 additions & 3 deletions nmrs/src/api/models/openvpn.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#![allow(deprecated)]

use super::vpn::{VpnConfig, VpnType};
use super::vpn::{VpnConfig, VpnKind};
use crate::api::models::error::ConnectionError;
use std::convert::TryFrom;
use std::net::Ipv4Addr;
Expand Down Expand Up @@ -612,8 +612,8 @@ impl TryFrom<crate::core::ovpn_parser::parser::OvpnFile> for OpenVpnConfig {
impl super::vpn::sealed::Sealed for OpenVpnConfig {}

impl VpnConfig for OpenVpnConfig {
fn vpn_type(&self) -> VpnType {
VpnType::OpenVpn
fn vpn_kind(&self) -> VpnKind {
VpnKind::Plugin
}

fn name(&self) -> &str {
Expand Down
6 changes: 3 additions & 3 deletions nmrs/src/api/models/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -607,7 +607,7 @@ fn test_vpn_credentials_builder_basic() {
.build();

assert_eq!(creds.name, "TestVPN");
assert_eq!(creds.vpn_type, VpnType::WireGuard);
assert_eq!(creds.vpn_type, VpnKind::WireGuard);
assert_eq!(creds.gateway, "vpn.example.com:51820");
assert_eq!(
creds.private_key,
Expand Down Expand Up @@ -667,7 +667,7 @@ fn test_wireguard_config_implements_vpn_config() {

let vpn_config: &dyn VpnConfig = &config;

assert_eq!(vpn_config.vpn_type(), VpnType::WireGuard);
assert_eq!(vpn_config.vpn_kind(), VpnKind::WireGuard);
assert_eq!(vpn_config.name(), "TestVPN");
assert_eq!(
vpn_config.dns(),
Expand Down Expand Up @@ -942,7 +942,7 @@ fn test_vpn_credentials_builder_equivalence_to_new() {
);

let creds_new = VpnCredentials::new(
VpnType::WireGuard,
VpnKind::WireGuard,
"TestVPN",
"vpn.example.com:51820",
"private_key",
Expand Down
Loading