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 docs/book.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ language = "en"
src = "src"

[rust]
edition = "2021"
edition = "2024"

[build]
build-dir = "book"
Expand Down
6 changes: 3 additions & 3 deletions docs/src/advanced/async-runtimes.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ Add to your `Cargo.toml`:

```toml
[dependencies]
nmrs = "2.2"
nmrs = "3.1"
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
```

Expand Down Expand Up @@ -55,7 +55,7 @@ async fn main() -> nmrs::Result<()> {

```toml
[dependencies]
nmrs = "2.2"
nmrs = "3.1"
async-std = { version = "1", features = ["attributes"] }
```

Expand All @@ -76,7 +76,7 @@ fn main() -> nmrs::Result<()> {

```toml
[dependencies]
nmrs = "2.2"
nmrs = "3.1"
smol = "2"
```

Expand Down
2 changes: 1 addition & 1 deletion docs/src/advanced/logging.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ nmrs produces log messages but doesn't configure a logger — that's up to your

```toml
[dependencies]
nmrs = "2.2"
nmrs = "3.1"
env_logger = "0.11"
log = "0.4"
```
Expand Down
74 changes: 53 additions & 21 deletions docs/src/api/errors.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,23 @@
# Error Types

nmrs uses a single error enum, `ConnectionError`, for all operations. It implements `std::error::Error`, `Display`, and `Debug`.
nmrs uses a single error enum, `ConnectionError`, for all operations. It
implements `std::error::Error`, `Display`, and `Debug`, and is also
re-exported as the source type for the [`nmrs::Result<T>`](./types.md#result-type)
alias.

## ConnectionError

```rust
#[non_exhaustive]
pub enum ConnectionError {
// D-Bus errors
// D-Bus
Dbus(zbus::Error),
DbusOperation { context: String, source: zbus::Error },

// Network not found
// Network discovery
NotFound,
ApBssidNotFound { ssid: String, bssid: String },
InvalidBssid(String),

// Authentication
AuthFailed,
Expand All @@ -24,49 +29,62 @@ pub enum ConnectionError {
DhcpFailed,
Timeout,
Stuck(String),
DeviceFailed(StateReason),
ActivationFailed(ConnectionStateReason),

// Device errors
// Devices
NoWifiDevice,
NoWiredDevice,
WifiNotReady,
NoBluetoothDevice,
NoSavedConnection,

// Device/activation failures with reason codes
DeviceFailed(StateReason),
ActivationFailed(ConnectionStateReason),

// Per-device errors
WifiInterfaceNotFound { interface: String },
NotAWifiDevice { interface: String },

// Radio errors
// Radios / airplane mode
HardwareRadioKilled,
BluezUnavailable,
BluezUnavailable(String),
BluetoothToggleFailed(String),

// VPN errors
// Saved profiles
NoSavedConnection,
SavedConnectionNotFound(String),
MalformedSavedConnection(String),
IncompleteBuilder(String),

// VPN
NoVpnConnection,
VpnNotFound(String),
VpnIdAmbiguous(String),
VpnFailed(String),
VpnIdAmbiguous { id: String },
IncompleteBuilder(String),
InvalidPrivateKey(String),
InvalidPublicKey(String),
InvalidAddress(String),
InvalidGateway(String),
InvalidPeers(String),
ParseError(OvpnParseError),

// VLAN
InvalidVlanId { id: u16 },

// Generic input validation
InvalidInput { field: String, reason: String },

// Connectivity
ConnectivityCheckDisabled,

// BSSID
ApBssidNotFound { ssid: String, bssid: String },
InvalidBssid(String),
// Secret agent
AgentRegistration { context: String },
AgentNotRegistered,
AgentAlreadyRegistered,

// Other
InvalidUtf8(Utf8Error),
// Encoding
InvalidUtf8(std::str::Utf8Error),
}
```

> `ConnectionError` is `#[non_exhaustive]`, so always include a wildcard
> arm in `match` expressions.

## Error Categories

### User-Facing Errors
Expand Down Expand Up @@ -109,6 +127,20 @@ These indicate infrastructure issues:
| `Stuck` | NetworkManager in unexpected state |
| `DeviceFailed` | Check the `StateReason` for details |
| `ActivationFailed` | Check the `ConnectionStateReason` for details |
| `BluezUnavailable` | BlueZ not running or no Bluetooth adapters present |
| `BluetoothToggleFailed` | Adapter exists but failed to power on/off |
| `MalformedSavedConnection` | Saved profile is missing required keys; consider deleting it |
| `AgentRegistration` | Secret agent failed to register; check `context` |

### Secret Agent Errors

These come from the [`agent`](../../agent/index.html) module:

| Error | Meaning |
|-------|---------|
| `AgentRegistration { context }` | `register()` failed (e.g. NetworkManager not reachable) |
| `AgentNotRegistered` | Tried to use a handle whose registration was already torn down |
| `AgentAlreadyRegistered` | Another `nmrs` agent in the process is using the same identifier |

## StateReason

Expand Down
35 changes: 27 additions & 8 deletions docs/src/api/models.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ pub struct Device {
}
```

Methods: `is_wireless()`, `is_wired()`, `is_bluetooth()`
Methods: `is_wireless()`, `is_wired()`, `is_bluetooth()`, `is_loopback()`, `is_vlan()`

### DeviceIdentity

Expand All @@ -42,6 +42,7 @@ pub enum DeviceType {
WifiP2P,
Loopback,
Bluetooth,
Vlan,
Other(u32),
}
```
Expand All @@ -65,20 +66,28 @@ Methods: `is_transitional()`

### Network

A discovered Wi-Fi network.
A discovered Wi-Fi network. Networks sharing an SSID on the same device
are grouped, keeping the strongest AP as the representative; the merged
peers are still recorded in `bssids`.

```rust
pub struct Network {
pub device: String,
pub ssid: String,
pub bssid: Option<String>,
pub strength: Option<u8>,
pub frequency: Option<u32>,
pub bssid: Option<String>, // best BSSID (strongest AP)
pub strength: Option<u8>, // 0..=100
pub frequency: Option<u32>, // MHz
pub secured: bool,
pub is_psk: bool,
pub is_eap: bool,
pub ip4_address: Option<String>,
pub is_hotspot: bool,
pub ip4_address: Option<String>, // populated only when this is the active network
pub ip6_address: Option<String>,
pub best_bssid: String, // mirror of `bssid` for the strongest AP
pub bssids: Vec<String>, // every BSSID seen for this SSID, strongest first
pub is_active: bool, // true if currently connected
pub known: bool, // true if a saved profile exists for this SSID
pub security_features: SecurityFeatures, // decoded security flag triplet
}
```

Expand All @@ -103,9 +112,15 @@ A Wi-Fi device discovered by `list_wifi_devices()`.

```rust
pub struct WifiDevice {
pub interface: String,
pub mac: String,
pub path: OwnedObjectPath,
pub interface: String, // e.g. "wlan0"
pub hw_address: String, // current MAC (may be randomized)
pub permanent_hw_address: Option<String>,
pub driver: Option<String>,
pub state: DeviceState,
pub managed: bool,
pub autoconnect: bool,
pub is_active: bool,
pub active_ssid: Option<String>,
}
```
Expand Down Expand Up @@ -407,9 +422,13 @@ pub struct BluetoothDevice {
pub struct BluetoothIdentity {
pub bdaddr: String,
pub bt_device_type: BluetoothNetworkRole,
pub adapter: Option<String>, // e.g. Some("hci1"); defaults to "hci0"
}
```

Constructors: `new(bdaddr, role)` and `with_adapter(bdaddr, role, adapter)`.
Both validate the MAC and return [`ConnectionError`] on bad input.

### BluetoothNetworkRole

```rust
Expand Down
2 changes: 1 addition & 1 deletion docs/src/api/types.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ All public methods return `nmrs::Result<T>`.
| `WireGuardPeer` | WireGuard peer configuration |
| `OpenVpnConfig` | OpenVPN configuration |
| `OpenVpnAuthType` | OpenVPN auth: `Password`, `Tls`, `PasswordTls`, `StaticKey` |
| `OpenVpnCompression` | Compression mode: `No`, `Lz4`, `Lz4V2`, `Yes` |
| `OpenVpnCompression` | Compression mode: `No`, `Lzo` (deprecated), `Lz4`, `Lz4V2`, `Yes` |
| `OpenVpnProxy` | Proxy: `Http { ... }`, `Socks { ... }` |
| `VpnRoute` | Static IPv4 route for split tunneling |
| `VpnType` | Protocol-specific metadata (data-carrying enum) |
Expand Down
72 changes: 51 additions & 21 deletions docs/src/appendix/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,32 +4,62 @@ See the full changelog on GitHub: [**nmrs** CHANGELOG](https://github.com/cacheb

## nmrs (Library) Highlights

### 2.2.0
### 3.1.0

- Concurrency protection — `is_connecting()` API
- `WirelessHardwareEnabled` property support
- BDADDR to BlueZ path resolution
- Mixed WPA1+WPA2 network support
- Loopback device support and a new `DeviceType::Vlan` variant
- VLAN (802.1Q) support: `VlanConfig` model and `build_vlan_connection`
- `RadioState::present` reports whether a radio actually exists on the host
- `airplane_mode_state()` and `set_airplane_mode()` are now correct on
Wi-Fi-only / Bluetooth-less hosts (BlueZ-missing is treated as a no-op)
- `set_bluetooth_radio_enabled` now waits for the adapter `Powered`
property to flip before returning

### 2.1.0
### 3.0.x

- `#[must_use]` annotations on public builder APIs
- `nmrs::agent` module — register a NetworkManager **secret agent**
(`SecretAgent`, `SecretAgentBuilder`, `SecretRequest`, `SecretResponder`,
…) for Wi-Fi/VPN/802.1X credential prompts over D-Bus
- Per-Wi-Fi-device scoping: `WifiDevice`, `list_wifi_devices()`,
`wifi_device_by_interface()`, and `nm.wifi("wlan1")` → `WifiScope`
- New `interface: Option<&str>` parameter on `connect`, `connect_to_bssid`,
`disconnect`, `scan_networks`, and `list_networks` (**3.0 break**)
- `set_wifi_enabled(interface, bool)` now toggles a single radio;
`set_wireless_enabled(bool)` is the global software killswitch
- Airplane-mode surface: `RadioState`, `AirplaneModeState`, `wifi_state`,
`wwan_state`, `bluetooth_radio_state`, plus rfkill awareness
- Connectivity surface: `connectivity()`, `check_connectivity()`,
`connectivity_report()`, `captive_portal_url()`, `ConnectivityCheckDisabled`
- Generic VPN model: `VpnType` is now data-carrying (WireGuard, OpenVPN,
OpenConnect, strongSwan, PPTP, L2TP, Generic). `VpnKind` distinguishes
plugin VPNs from kernel WireGuard. `VpnConnection` gained `uuid`,
`active`, `user_name`, `password_flags`, `service_type`. New
`connect_vpn_by_uuid`, `connect_vpn_by_id`, `disconnect_vpn_by_uuid`,
`active_vpn_connections`.
- OpenVPN end-to-end: full `OpenVpnConfig` with TLS hardening, ciphers,
proxy, routes, `redirect_gateway`; `OpenVpnBuilder`; `.ovpn` import
via `nm.import_ovpn(path, user, pass)`; cert-store handling for inline
certs.
- Saved profile management: `list_saved_connections{,_brief,_ids}`,
`get_saved_connection{,_raw}`, `delete_saved_connection`,
`update_saved_connection`, `reload_saved_connections`,
`SavedConnection`, `SavedConnectionBrief`, `SettingsSummary`,
`SettingsPatch`.
- `AccessPoint` model + `list_access_points(interface)` for per-BSSID
enumeration.
- `ConnectionError::IncompleteBuilder` for builders missing required
fields; `Builder::build()` returns `Result` instead of panicking.
- Edition 2024, MSRV bumped to 1.90.0 (3.0.1).

### 2.0.1
### 2.x

- IPv6 address support for devices and networks
- `WifiMode` enum for builder API
- Input validation for SSIDs, credentials, and addresses
- Idempotent `forget_vpn()` behavior

### 2.0.0

- Bluetooth support (PAN and DUN)
- Configurable timeouts via `TimeoutConfig`
- `VpnCredentials` and `EapOptions` builder patterns
- `ConnectionOptions` for autoconnect configuration
- `ConnectionBuilder` for advanced connection settings
- `WireGuardBuilder` with validation
- Concurrency protection (`is_connecting()`), `WirelessHardwareEnabled`,
BDADDR → BlueZ path resolution, mixed-mode WPA1+WPA2 (2.2.0)
- `#[must_use]` annotations on public builder APIs (2.1.0)
- IPv6 address support, `WifiMode` builder, input validation for SSIDs/
credentials/addresses, idempotent `forget_vpn()` (2.0.1)
- Bluetooth support (PAN and DUN), configurable `TimeoutConfig`,
`VpnCredentials` / `EapOptions` builder patterns, `ConnectionOptions`,
`ConnectionBuilder`, `WireGuardBuilder` with validation (2.0.0)

### 1.x

Expand Down
5 changes: 4 additions & 1 deletion docs/src/appendix/faq.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ nmrs is a Rust library for managing network connections on Linux via NetworkMana

### Is nmrs production-ready?

Yes. nmrs is at version 2.2.0 with a stable API. All public types are also `#[non_exhaustive]` to allow backward-compatible additions.
Yes. nmrs is at version 3.1.x with a stable API. All public types are
marked `#[non_exhaustive]` to allow backward-compatible additions, and
the public surface is enforced in CI with
[`cargo-semver-checks`](https://crates.io/crates/cargo-semver-checks).

### What Linux distributions are supported?

Expand Down
2 changes: 1 addition & 1 deletion docs/src/examples/wifi-scanner.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ Add to your `Cargo.toml`:

```toml
[dependencies]
nmrs = "2.0.0"
nmrs = "3.1"
tokio = { version = "1", features = ["full"] }
```

Expand Down
8 changes: 7 additions & 1 deletion docs/src/getting-started/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,15 @@ Or manually add to your `Cargo.toml`:

```toml
[dependencies]
nmrs = "2.0.0"
nmrs = "3.1"
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
```

`nmrs` is async and ships no runtime of its own. The examples in this book
use [Tokio](https://tokio.rs/) but `nmrs` works with any reactor that is
compatible with the [`zbus`](https://docs.rs/zbus) executor (Tokio,
`async-std`, `smol`, …). See [Async Runtime Support](../advanced/async-runtimes.md).

### From Source

Clone and build from source:
Expand Down
Loading
Loading