nmrs

Rust bindings for NetworkManager via D-Bus.
Why?
nmrs provides a high-level, async API for managing Wi-Fi connections on Linux systems. It abstracts the complexity of D-Bus communication with NetworkManager, offering typed error handling and an ergonomic interface.
Features
- WiFi Management: Connect to WPA-PSK, WPA-EAP, and open networks
- 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
- Profile Management: List saved profiles with decoded summaries (
list_saved_connections), raw settings, UUID-based delete/update, plus create/query/delete helpers
- Real-Time Monitoring: Signal-based network and device state change notifications
- Secret Agent: Respond to NetworkManager credential prompts via an async stream API
- Airplane Mode: Toggle Wi-Fi, WWAN, and Bluetooth radios with rfkill hardware awareness
- Connectivity: Query NM's connectivity state, force re-checks, and detect captive-portal URLs
- Typed Errors: Structured error types with specific failure reasons
- Fully Async: Built on
zbus with async/await throughout
Installation
[dependencies]
nmrs = "3.0.0"
or
cargo add nmrs
Quick Start
Below are a few examples of the different ways to interface with things like WiFi (wireless) devices, WireGuard VPN configs, or EAP connections.
WiFi Connection
use nmrs::{NetworkManager, WifiSecurity};
#[tokio::main]
async fn main() -> nmrs::Result<()> {
let nm = NetworkManager::new().await?;
let networks = nm.list_networks(None).await?;
for net in &networks {
println!("{} - Signal: {}%", net.ssid, net.strength.unwrap_or(0));
}
nm.connect("MyNetwork", None, WifiSecurity::WpaPsk {
psk: "password".into()
}).await?;
let wlan1 = nm.wifi("wlan1");
wlan1.scan().await?;
wlan1.connect("Guest", WifiSecurity::Open).await?;
if let Some(ssid) = nm.current_ssid().await {
println!("Connected to: {}", ssid);
}
Ok(())
}
WireGuard VPN
use nmrs::{NetworkManager, WireGuardConfig, WireGuardPeer};
#[tokio::main]
async fn main() -> nmrs::Result<()> {
let nm = NetworkManager::new().await?;
let peer = WireGuardPeer::new(
"server_public_key",
"vpn.example.com:51820",
vec!["0.0.0.0/0".into()],
).with_persistent_keepalive(25);
let config = WireGuardConfig::new(
"WorkVPN",
"vpn.example.com:51820",
"your_private_key_here",
"10.0.0.2/24",
vec![peer],
).with_dns(vec!["1.1.1.1".into()]);
nm.connect_vpn(config).await?;
let info = nm.get_vpn_info("WorkVPN").await?;
println!("VPN IP: {:?}", info.ip4_address);
nm.disconnect_vpn("WorkVPN").await?;
Ok(())
}
WPA-Enterprise (EAP)
use nmrs::{NetworkManager, WifiSecurity, EapOptions, EapMethod, Phase2};
#[tokio::main]
async fn main() -> nmrs::Result<()> {
let nm = NetworkManager::new().await?;
nm.connect("CorpNetwork", None, WifiSecurity::WpaEap {
opts: EapOptions {
identity: "user@company.com".into(),
password: "password".into(),
anonymous_identity: None,
domain_suffix_match: Some("company.com".into()),
ca_cert_path: None,
system_ca_certs: true,
method: EapMethod::Peap,
phase2: Phase2::Mschapv2,
}
}).await?;
Ok(())
}
Device Management
We also handle agnostic device management, as many of the devicees supported by NetworkManager can queried in similar ways.
use nmrs::NetworkManager;
#[tokio::main]
async fn main() -> nmrs::Result<()> {
let nm = NetworkManager::new().await?;
let devices = nm.list_devices().await?;
for device in devices {
println!("{}: {} ({})", device.interface, device.device_type, device.state);
}
nm.set_wireless_enabled(false).await?;
nm.set_wireless_enabled(true).await?;
Ok(())
}
Secret Agent
Register as a NetworkManager secret agent to handle credential prompts
(Wi-Fi passwords, VPN tokens, 802.1X credentials):
use futures::StreamExt;
use nmrs::agent::{SecretAgent, SecretAgentFlags, SecretSetting};
#[tokio::main]
async fn main() -> nmrs::Result<()> {
let (handle, mut requests) = SecretAgent::builder()
.with_identifier("com.example.my_app")
.register()
.await?;
while let Some(req) = requests.next().await {
if let SecretSetting::WifiPsk { ref ssid } = req.setting {
println!("Password needed for {ssid}");
req.responder.wifi_psk("my-password").await?;
} else {
req.responder.cancel().await?;
}
}
handle.unregister().await?;
Ok(())
}
Real-Time Monitoring
use nmrs::NetworkManager;
#[tokio::main]
async fn main() -> nmrs::Result<()> {
let nm = NetworkManager::new().await?;
nm.monitor_network_changes(|| {
println!("Network list changed");
}).await?;
Ok(())
}
Error Handling
All operations return Result<T, ConnectionError> with specific variants:
use nmrs::{NetworkManager, WifiSecurity, ConnectionError};
match nm.connect("MyNetwork", None, WifiSecurity::WpaPsk {
psk: "wrong".into()
}).await {
Ok(_) => println!("Connected"),
Err(ConnectionError::AuthFailed) => eprintln!("Authentication failed"),
Err(ConnectionError::NotFound) => eprintln!("Network not in range"),
Err(ConnectionError::Timeout) => eprintln!("Connection timed out"),
Err(ConnectionError::DhcpFailed) => eprintln!("Failed to obtain IP address"),
Err(e) => eprintln!("Error: {}", e),
}
Async Runtime Support
nmrs is runtime-agnostic and works with any async runtime:
- Tokio
- async-std
- smol
- Any runtime supporting standard Rust
async/await
All examples use Tokio, but you can use your preferred runtime:
With Tokio:
#[tokio::main]
async fn main() -> nmrs::Result<()> {
let nm = nmrs::NetworkManager::new().await?;
Ok(())
}
With async-std:
#[async_std::main]
async fn main() -> nmrs::Result<()> {
let nm = nmrs::NetworkManager::new().await?;
Ok(())
}
With smol:
fn main() -> nmrs::Result<()> {
smol::block_on(async {
let nm = nmrs::NetworkManager::new().await?;
Ok(())
})
}
nmrs uses zbus for D-Bus communication, which launches a background thread to handle D-Bus message processing. This design ensures compatibility across all async runtimes without requiring manual executor management.
Documentation
Complete API documentation: docs.rs/nmrs
Requirements
- Linux with NetworkManager (1.0+)
- D-Bus system bus access
- Appropriate permissions for network management
Logging
Enable logging via the log crate:
env_logger::init();
Set RUST_LOG=nmrs=debug for detailed logs.
License
MIT