nmrs 3.1.0

A Rust library for NetworkManager over D-Bus
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
use std::fmt::{Display, Formatter};

use zvariant::OwnedObjectPath;

/// Represents a network device managed by NetworkManager.
///
/// A device can be a WiFi adapter, Ethernet interface, or other network hardware.
///
/// # Examples
///
/// ```no_run
/// use nmrs::NetworkManager;
///
/// # async fn example() -> nmrs::Result<()> {
/// let nm = NetworkManager::new().await?;
/// let devices = nm.list_devices().await?;
///
/// for device in devices {
///     println!("Interface: {}", device.interface);
///     println!("  Type: {}", device.device_type);
///     println!("  State: {}", device.state);
///     println!("  MAC: {}", device.identity.current_mac);
///
///     if device.is_wireless() {
///         println!("  This is a WiFi device");
///     } else if device.is_wired() {
///         println!("  This is an Ethernet device");
///     } else if device.is_bluetooth() {
///         println!("  This is a Bluetooth device");
///     } else if device.is_loopback() {
///         println!("  This is a loopback device");
///     }
///
///     if let Some(driver) = &device.driver {
///         println!("  Driver: {}", driver);
///     }
/// }
/// # Ok(())
/// # }
/// ```
#[non_exhaustive]
#[derive(Debug, Clone)]
pub struct Device {
    /// D-Bus object path
    pub path: String,
    /// Interface name (e.g., "wlan0", "eth0")
    pub interface: String,
    /// Device hardware identity (MAC addresses)
    pub identity: DeviceIdentity,
    /// Type of device (WiFi, Ethernet, etc.)
    pub device_type: DeviceType,
    /// Current device state
    pub state: DeviceState,
    /// Whether NetworkManager manages this device
    pub managed: Option<bool>,
    /// Kernel driver name
    pub driver: Option<String>,
    /// Assigned IPv4 address with CIDR notation (only present when connected)
    pub ip4_address: Option<String>,
    /// Assigned IPv6 address with CIDR notation (only present when connected)
    pub ip6_address: Option<String>,
    // Link speed in Mb/s (wired devices)
    // pub speed: Option<u32>,
}

/// A Wi-Fi device summary returned by
/// [`list_wifi_devices`](crate::NetworkManager::list_wifi_devices).
///
/// Use this on multi-radio machines (laptops with USB dongles, docks with a
/// second wireless adapter, etc.) to discover the available interfaces and
/// pick one to scope subsequent operations to. Pair with
/// [`NetworkManager::wifi`](crate::NetworkManager::wifi) for ergonomic
/// per-interface calls.
#[non_exhaustive]
#[derive(Debug, Clone)]
pub struct WifiDevice {
    /// D-Bus object path of the device.
    pub path: OwnedObjectPath,
    /// Interface name (e.g. `"wlan0"`).
    pub interface: String,
    /// Current MAC address (may be randomized).
    pub hw_address: String,
    /// Permanent (factory-burned) MAC, if NM exposes it.
    pub permanent_hw_address: Option<String>,
    /// Kernel driver name, if available.
    pub driver: Option<String>,
    /// Current device state.
    pub state: DeviceState,
    /// Whether NetworkManager manages this device.
    pub managed: bool,
    /// Whether NM will autoconnect known networks on this device.
    pub autoconnect: bool,
    /// `true` if the device currently has an active access point.
    pub is_active: bool,
    /// SSID of the currently active AP, if any.
    pub active_ssid: Option<String>,
}

/// Represents the hardware identity of a network device.
///
/// Contains MAC addresses that uniquely identify the device. The permanent
/// MAC is burned into the hardware, while the current MAC may be different
/// if MAC address randomization or spoofing is enabled.
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct DeviceIdentity {
    /// The permanent (factory-assigned) MAC address.
    pub permanent_mac: String,
    /// The current MAC address in use (may differ if randomized/spoofed).
    pub current_mac: String,
}

impl DeviceIdentity {
    /// Creates a new `DeviceIdentity`.
    ///
    /// # Arguments
    ///
    /// * `permanent_mac` - The permanent (factory-assigned) MAC address
    /// * `current_mac` - The current MAC address in use
    #[must_use]
    pub fn new(permanent_mac: String, current_mac: String) -> Self {
        Self {
            permanent_mac,
            current_mac,
        }
    }
}

/// 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.
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq)]
pub enum DeviceType {
    /// Wired Ethernet device.
    Ethernet,
    /// Wi-Fi (802.11) wireless device.
    Wifi,
    /// Wi-Fi P2P (peer-to-peer) device.
    WifiP2P,
    /// Loopback device (localhost).
    Loopback,
    /// Bluetooth
    Bluetooth,
    /// VLAN (802.1Q) virtual device.
    Vlan,
    /// 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.
    #[must_use]
    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.
    #[must_use]
    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.
    #[must_use]
    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.
    #[must_use]
    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::Bluetooth => "bluetooth",
            Self::Vlan => "vlan",
            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.
    #[must_use]
    pub fn to_code(&self) -> u32 {
        match self {
            Self::Ethernet => 1,
            Self::Wifi => 2,
            Self::WifiP2P => 30,
            Self::Loopback => 32,
            Self::Bluetooth => 6,
            Self::Vlan => 11,
            Self::Other(code) => *code,
        }
    }
}

/// NetworkManager device states.
///
/// Represents the current operational state of a network device.
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq)]
pub enum DeviceState {
    /// Device is not managed by NetworkManager.
    Unmanaged,
    /// Device is managed but not yet available (e.g., Wi-Fi disabled).
    Unavailable,
    /// Device is available but not connected.
    Disconnected,
    /// Device is preparing to connect.
    Prepare,
    /// Device is being configured.
    Config,
    /// Device requires authentication credentials.
    NeedAuth,
    /// Device is requesting IP configuration.
    IpConfig,
    /// Device is verifying IP connectivity.
    IpCheck,
    /// Device is waiting for secondary connections.
    Secondaries,
    /// Device is fully connected and operational.
    Activated,
    /// Device is disconnecting.
    Deactivating,
    /// Device connection failed.
    Failed,
    /// Unknown or unsupported state with raw code.
    Other(u32),
}

impl DeviceState {
    /// Returns `true` if the device is in a transitional (in-progress) state.
    ///
    /// Transitional states indicate an active connection or disconnection
    /// operation: Prepare, Config, NeedAuth, IpConfig, IpCheck, Secondaries,
    /// or Deactivating.
    #[must_use]
    pub fn is_transitional(&self) -> bool {
        matches!(
            self,
            Self::Prepare
                | Self::Config
                | Self::NeedAuth
                | Self::IpConfig
                | Self::IpCheck
                | Self::Secondaries
                | Self::Deactivating
        )
    }
}

impl Device {
    /// Returns `true` if this is a wired (Ethernet) device.
    #[must_use]
    pub fn is_wired(&self) -> bool {
        matches!(self.device_type, DeviceType::Ethernet)
    }

    /// Returns `true` if this is a wireless (Wi-Fi) device.
    #[must_use]
    pub fn is_wireless(&self) -> bool {
        matches!(self.device_type, DeviceType::Wifi)
    }

    /// Returns 'true' if this is a Bluetooth (DUN or PANU) device.
    #[must_use]
    pub fn is_bluetooth(&self) -> bool {
        matches!(self.device_type, DeviceType::Bluetooth)
    }

    /// Returns `true` if this is a loopback device (e.g., `lo`).
    #[must_use]
    pub fn is_loopback(&self) -> bool {
        matches!(self.device_type, DeviceType::Loopback)
    }

    /// Returns `true` if this is a VLAN (802.1Q) device.
    #[must_use]
    pub fn is_vlan(&self) -> bool {
        matches!(self.device_type, DeviceType::Vlan)
    }
}

impl Display for Device {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "{} ({}) [{}]",
            self.interface, self.device_type, self.state
        )
    }
}

impl From<u32> for DeviceType {
    fn from(value: u32) -> Self {
        match value {
            1 => DeviceType::Ethernet,
            2 => DeviceType::Wifi,
            5 => DeviceType::Bluetooth,
            11 => DeviceType::Vlan,
            30 => DeviceType::WifiP2P,
            32 => DeviceType::Loopback,
            v => DeviceType::Other(v),
        }
    }
}

impl From<u32> for DeviceState {
    fn from(value: u32) -> Self {
        match value {
            10 => Self::Unmanaged,
            20 => Self::Unavailable,
            30 => Self::Disconnected,
            40 => Self::Prepare,
            50 => Self::Config,
            60 => Self::NeedAuth,
            70 => Self::IpConfig,
            80 => Self::IpCheck,
            90 => Self::Secondaries,
            100 => Self::Activated,
            110 => Self::Deactivating,
            120 => Self::Failed,
            v => Self::Other(v),
        }
    }
}

impl Display for DeviceType {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        match self {
            DeviceType::Ethernet => write!(f, "Ethernet"),
            DeviceType::Wifi => write!(f, "Wi-Fi"),
            DeviceType::WifiP2P => write!(f, "Wi-Fi P2P"),
            DeviceType::Loopback => write!(f, "Loopback"),
            DeviceType::Bluetooth => write!(f, "Bluetooth"),
            DeviceType::Vlan => write!(f, "VLAN"),
            DeviceType::Other(v) => write!(
                f,
                "{}",
                crate::types::device_type_registry::display_name_for_code(*v)
            ),
        }
    }
}

impl Display for DeviceState {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::Unmanaged => write!(f, "Unmanaged"),
            Self::Unavailable => write!(f, "Unavailable"),
            Self::Disconnected => write!(f, "Disconnected"),
            Self::Prepare => write!(f, "Preparing"),
            Self::Config => write!(f, "Configuring"),
            Self::NeedAuth => write!(f, "NeedAuth"),
            Self::IpConfig => write!(f, "IpConfig"),
            Self::IpCheck => write!(f, "IpCheck"),
            Self::Secondaries => write!(f, "Secondaries"),
            Self::Activated => write!(f, "Activated"),
            Self::Deactivating => write!(f, "Deactivating"),
            Self::Failed => write!(f, "Failed"),
            Self::Other(v) => write!(f, "Other({v})"),
        }
    }
}