nmrs 3.0.0

A Rust library for NetworkManager over D-Bus
Documentation

nmrs

Crates.io Documentation License

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?;
    
    // List networks (None = all Wi-Fi devices, or pass Some("wlan1") to scope)
    let networks = nm.list_networks(None).await?;
    for net in &networks {
        println!("{} - Signal: {}%", net.ssid, net.strength.unwrap_or(0));
    }

    // Connect to a WPA-PSK network on the first Wi-Fi device
    nm.connect("MyNetwork", None, WifiSecurity::WpaPsk {
        psk: "password".into()
    }).await?;

    // Or scope every operation to a specific radio
    let wlan1 = nm.wifi("wlan1");
    wlan1.scan().await?;
    wlan1.connect("Guest", WifiSecurity::Open).await?;
    
    // Check current connection
    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()]);
    
    // Connect to VPN
    nm.connect_vpn(config).await?;
    
    // Get connection details
    let info = nm.get_vpn_info("WorkVPN").await?;
    println!("VPN IP: {:?}", info.ip4_address);
    
    // Disconnect
    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?;
    
    // List all network devices
    let devices = nm.list_devices().await?;
    for device in devices {
        println!("{}: {} ({})", device.interface, device.device_type, device.state);
    }
    
    // Control the global Wi-Fi radio
    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?;
    
    // Monitor network changes
    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