Skip to main content

wayle_network/core/device/
mod.rs

1mod controls;
2mod monitoring;
3mod types;
4/// WiFi device functionality and management.
5pub mod wifi;
6/// Wired (ethernet) device functionality and management.
7pub mod wired;
8
9use std::{collections::HashMap, sync::Arc};
10
11use controls::DeviceControls;
12use derive_more::Debug;
13use futures::{Stream, StreamExt};
14use tokio_util::sync::CancellationToken;
15pub use types::DeviceStateChangedEvent;
16use types::{AppliedConnection, DeviceProperties};
17pub(crate) use types::{DeviceParams, LiveDeviceParams};
18use wayle_core::{Property, unwrap_dbus, unwrap_dbus_or};
19use wayle_traits::{ModelMonitoring, Reactive};
20use zbus::{
21    Connection,
22    zvariant::{OwnedObjectPath, OwnedValue},
23};
24
25use crate::{
26    error::Error,
27    proxy::devices::DeviceProxy,
28    types::{
29        connectivity::{NMConnectivityState, NMMetered},
30        device::{LldpNeighbor, NMDeviceManaged, NMDeviceManagedFlags, NMDeviceType},
31        flags::{NMDeviceCapabilities, NMDeviceInterfaceFlags},
32        states::{NMDeviceState, NMDeviceStateReason},
33    },
34};
35
36/// Network device managed by NetworkManager.
37///
38/// Common functionality for all network interfaces (WiFi, ethernet, etc).
39/// Contains hardware information, state, configuration, and statistics.
40#[derive(Debug, Clone)]
41pub struct Device {
42    #[debug(skip)]
43    pub(crate) connection: Connection,
44    #[debug(skip)]
45    pub(crate) cancellation_token: Option<CancellationToken>,
46
47    /// D-Bus object path for this device.
48    pub object_path: OwnedObjectPath,
49
50    /// Operating-system specific transient device hardware identifier. Opaque
51    /// string representing the underlying hardware for the device, and shouldn't be used to
52    /// keep track of individual devices. For some device types (Bluetooth, Modems) it is an
53    /// identifier used by the hardware service (eg bluez or ModemManager) to refer to that
54    /// device, and client programs use it get additional information from those services
55    /// which NM does not provide. The Udi is not guaranteed to be consistent across reboots
56    /// or hotplugs of the hardware.
57    pub udi: Property<String>,
58
59    /// The path of the device as exposed by the udev property ID_PATH.
60    pub udev_path: Property<String>,
61
62    /// The name of the device's control (and often data) interface. Note that non UTF-8
63    /// characters are backslash escaped, so the resulting name may be longer then 15
64    /// characters. Use g_strcompress() to revert the escaping.
65    pub interface: Property<String>,
66
67    /// The name of the device's data interface when available. May not refer
68    /// to the actual data interface until the device has successfully established a data
69    /// connection, indicated by the device's State becoming ACTIVATED. Note that non UTF-8
70    /// characters are backslash escaped, so the resulting name may be longer then 15
71    /// characters. Use g_strcompress() to revert the escaping.
72    pub ip_interface: Property<String>,
73
74    /// The driver handling the device. Non-UTF-8 sequences are backslash escaped.
75    pub driver: Property<String>,
76
77    /// The version of the driver handling the device. Non-UTF-8 sequences are backslash
78    /// escaped.
79    pub driver_version: Property<String>,
80
81    /// The firmware version for the device. Non-UTF-8 sequences are backslash escaped.
82    pub firmware_version: Property<String>,
83
84    /// Flags describing the capabilities of the device. See NMDeviceCapabilities.
85    pub capabilities: Property<NMDeviceCapabilities>,
86
87    /// The current state of the device.
88    pub state: Property<NMDeviceState>,
89
90    /// The current state and reason for that state.
91    pub state_reason: Property<(NMDeviceState, NMDeviceStateReason)>,
92
93    /// Object path of an ActiveConnection object that "owns" this device during activation.
94    /// The ActiveConnection object tracks the life-cycle of a connection to a specific
95    /// network and implements the org.freedesktop.NetworkManager.Connection.Active D-Bus
96    /// interface.
97    pub active_connection: Property<OwnedObjectPath>,
98
99    /// Object path of the Ip4Config object describing the configuration of the device. Only
100    /// valid when the device is in the NM_DEVICE_STATE_ACTIVATED state.
101    pub ip4_config: Property<OwnedObjectPath>,
102
103    /// Object path of the Dhcp4Config object describing the DHCP options returned by the
104    /// DHCP server. Only valid when the device is in the NM_DEVICE_STATE_ACTIVATED state.
105    pub dhcp4_config: Property<OwnedObjectPath>,
106
107    /// Object path of the Ip6Config object describing the configuration of the device. Only
108    /// valid when the device is in the NM_DEVICE_STATE_ACTIVATED state.
109    pub ip6_config: Property<OwnedObjectPath>,
110
111    /// Object path of the Dhcp6Config object describing the DHCP options returned by the
112    /// DHCP server. Only valid when the device is in the NM_DEVICE_STATE_ACTIVATED state.
113    pub dhcp6_config: Property<OwnedObjectPath>,
114
115    /// Whether or not this device is managed by NetworkManager. Setting this property has a
116    /// similar effect to configuring the device as unmanaged via the
117    /// keyfile.unmanaged-devices setting in NetworkManager.conf.
118    pub managed: Property<bool>,
119
120    /// If TRUE, indicates the device is allowed to autoconnect. If FALSE, manual
121    /// intervention is required before the device will automatically connect to a known
122    /// network, such as activating a connection using the device, or setting this property
123    /// to TRUE.
124    pub autoconnect: Property<bool>,
125
126    /// If TRUE, indicates the device is likely missing firmware necessary for its
127    /// operation.
128    pub firmware_missing: Property<bool>,
129
130    /// If TRUE, indicates the NetworkManager plugin for the device is likely missing or
131    /// misconfigured.
132    pub nm_plugin_missing: Property<bool>,
133
134    /// The general type of the network device.
135    pub device_type: Property<NMDeviceType>,
136
137    /// An array of object paths of every configured connection that is currently 'available'
138    /// through this device.
139    pub available_connections: Property<Vec<OwnedObjectPath>>,
140
141    /// If non-empty, an (opaque) indicator of the physical network port associated with the
142    /// device. Can be used to recognize when two seemingly-separate hardware devices
143    /// are actually just different virtual interfaces to the same physical port.
144    pub physical_port_id: Property<String>,
145
146    /// The MTU of the device.
147    pub mtu: Property<u32>,
148
149    /// Whether the amount of traffic flowing through the device is subject to limitations,
150    /// for example set by service providers.
151    pub metered: Property<NMMetered>,
152
153    /// Array of LLDP neighbors; each element is a dictionary mapping LLDP TLV names to
154    /// variant boxed values.
155    pub lldp_neighbors: Property<Vec<LldpNeighbor>>,
156
157    /// True if the device exists, or False for placeholder devices that do not yet exist but
158    /// could be automatically created by NetworkManager if one of their
159    /// AvailableConnections was activated.
160    pub real: Property<bool>,
161
162    /// The result of the last IPv4 connectivity check.
163    pub ip4_connectivity: Property<NMConnectivityState>,
164
165    /// The result of the last IPv6 connectivity check.
166    pub ip6_connectivity: Property<NMConnectivityState>,
167
168    /// The flags of the network interface. See NMDeviceInterfaceFlags for the currently
169    /// defined flags.
170    pub interface_flags: Property<NMDeviceInterfaceFlags>,
171
172    /// The hardware address of the device.
173    pub hw_address: Property<String>,
174
175    /// The port devices of the controller device. Array of object paths of port devices for
176    /// controller devices. For devices that are not controllers this is an empty array.
177    pub ports: Property<Vec<OwnedObjectPath>>,
178}
179
180impl Reactive for Device {
181    type Context<'a> = DeviceParams<'a>;
182    type LiveContext<'a> = LiveDeviceParams<'a>;
183    type Error = Error;
184
185    async fn get(params: Self::Context<'_>) -> Result<Self, Self::Error> {
186        Self::from_path(params.connection, params.object_path, None).await
187    }
188
189    async fn get_live(params: Self::LiveContext<'_>) -> Result<Arc<Self>, Self::Error> {
190        let device = Self::from_path(
191            params.connection,
192            params.object_path.clone(),
193            Some(params.cancellation_token.child_token()),
194        )
195        .await
196        .map_err(|e| Error::ObjectCreationFailed {
197            object_type: String::from("Device"),
198            object_path: params.object_path.clone(),
199            source: e.into(),
200        })?;
201
202        let device = Arc::new(device);
203        device.clone().start_monitoring().await?;
204
205        Ok(device)
206    }
207}
208
209impl Device {
210    pub(crate) async fn from_path(
211        connection: &Connection,
212        object_path: OwnedObjectPath,
213        cancellation_token: Option<CancellationToken>,
214    ) -> Result<Self, Error> {
215        let proxy = DeviceProxy::new(connection, &object_path).await?;
216        let props = Self::fetch_properties(&proxy).await?;
217        Ok(Self::from_properties(
218            props,
219            connection,
220            object_path,
221            cancellation_token,
222        ))
223    }
224
225    #[allow(clippy::too_many_lines)]
226    async fn fetch_properties(proxy: &DeviceProxy<'_>) -> Result<DeviceProperties, Error> {
227        let (udi, path, interface, ip_interface, driver, driver_version, firmware_version) = tokio::join!(
228            proxy.udi(),
229            proxy.path(),
230            proxy.interface(),
231            proxy.ip_interface(),
232            proxy.driver(),
233            proxy.driver_version(),
234            proxy.firmware_version(),
235        );
236
237        let (
238            capabilities,
239            state,
240            state_reason,
241            active_connection,
242            ip4_config,
243            dhcp4_config,
244            ip6_config,
245            dhcp6_config,
246        ) = tokio::join!(
247            proxy.capabilities(),
248            proxy.state(),
249            proxy.state_reason(),
250            proxy.active_connection(),
251            proxy.ip4_config(),
252            proxy.dhcp4_config(),
253            proxy.ip6_config(),
254            proxy.dhcp6_config(),
255        );
256
257        let (
258            managed,
259            autoconnect,
260            firmware_missing,
261            nm_plugin_missing,
262            device_type,
263            available_connections,
264            physical_port_id,
265            mtu,
266        ) = tokio::join!(
267            proxy.managed(),
268            proxy.autoconnect(),
269            proxy.firmware_missing(),
270            proxy.nm_plugin_missing(),
271            proxy.device_type(),
272            proxy.available_connections(),
273            proxy.physical_port_id(),
274            proxy.mtu(),
275        );
276
277        let (
278            metered,
279            real,
280            ip4_connectivity,
281            ip6_connectivity,
282            interface_flags,
283            hw_address,
284            ports,
285            _lldp_neighbors,
286        ) = tokio::join!(
287            proxy.metered(),
288            proxy.real(),
289            proxy.ip4_connectivity(),
290            proxy.ip6_connectivity(),
291            proxy.interface_flags(),
292            proxy.hw_address(),
293            proxy.ports(),
294            proxy.lldp_neighbors(),
295        );
296
297        let device_path = path.clone().unwrap_or_default();
298
299        let available_connections: Vec<OwnedObjectPath> =
300            unwrap_dbus!(available_connections, device_path)
301                .into_iter()
302                .map(|p| OwnedObjectPath::try_from(p.to_string()).unwrap_or_default())
303                .collect();
304
305        let ports: Vec<OwnedObjectPath> = unwrap_dbus!(ports, device_path)
306            .into_iter()
307            .map(|p| OwnedObjectPath::try_from(p.to_string()).unwrap_or_default())
308            .collect();
309
310        Ok(DeviceProperties {
311            udi: unwrap_dbus!(udi, device_path),
312            interface: unwrap_dbus!(interface, device_path),
313            ip_interface: unwrap_dbus!(ip_interface, device_path),
314            driver: unwrap_dbus!(driver, device_path),
315            driver_version: unwrap_dbus!(driver_version, device_path),
316            firmware_version: unwrap_dbus!(firmware_version, device_path),
317            capabilities: unwrap_dbus!(capabilities, device_path),
318            state: unwrap_dbus!(state, device_path),
319            state_reason: state_reason.unwrap_or((0, 0)),
320            active_connection: unwrap_dbus!(active_connection, device_path),
321            ip4_config: unwrap_dbus!(ip4_config, device_path),
322            dhcp4_config: unwrap_dbus!(dhcp4_config, device_path),
323            ip6_config: unwrap_dbus!(ip6_config, device_path),
324            dhcp6_config: unwrap_dbus!(dhcp6_config, device_path),
325            managed: unwrap_dbus_or!(managed, device_path, true),
326            autoconnect: unwrap_dbus!(autoconnect, device_path),
327            firmware_missing: unwrap_dbus!(firmware_missing, device_path),
328            nm_plugin_missing: unwrap_dbus!(nm_plugin_missing, device_path),
329            device_type: unwrap_dbus!(device_type, device_path),
330            available_connections,
331            physical_port_id: unwrap_dbus!(physical_port_id, device_path),
332            mtu: unwrap_dbus_or!(mtu, device_path, 1500),
333            metered: unwrap_dbus!(metered, device_path),
334            real: unwrap_dbus_or!(real, device_path, true),
335            ip4_connectivity: unwrap_dbus!(ip4_connectivity, device_path),
336            ip6_connectivity: unwrap_dbus!(ip6_connectivity, device_path),
337            interface_flags: unwrap_dbus!(interface_flags, device_path),
338            hw_address: unwrap_dbus!(hw_address, device_path),
339            ports,
340            udev_path: device_path,
341        })
342    }
343
344    fn from_properties(
345        props: DeviceProperties,
346        connection: &Connection,
347        object_path: OwnedObjectPath,
348        cancellation_token: Option<CancellationToken>,
349    ) -> Self {
350        Self {
351            cancellation_token,
352            connection: connection.clone(),
353            object_path,
354            udi: Property::new(props.udi),
355            udev_path: Property::new(props.udev_path),
356            interface: Property::new(props.interface),
357            ip_interface: Property::new(props.ip_interface),
358            driver: Property::new(props.driver),
359            driver_version: Property::new(props.driver_version),
360            firmware_version: Property::new(props.firmware_version),
361            capabilities: Property::new(NMDeviceCapabilities::from_bits_truncate(
362                props.capabilities,
363            )),
364            state: Property::new(NMDeviceState::from_u32(props.state)),
365            state_reason: Property::new((
366                NMDeviceState::from_u32(props.state_reason.0),
367                NMDeviceStateReason::from_u32(props.state_reason.1),
368            )),
369            active_connection: Property::new(props.active_connection),
370            ip4_config: Property::new(props.ip4_config),
371            dhcp4_config: Property::new(props.dhcp4_config),
372            ip6_config: Property::new(props.ip6_config),
373            dhcp6_config: Property::new(props.dhcp6_config),
374            managed: Property::new(props.managed),
375            autoconnect: Property::new(props.autoconnect),
376            firmware_missing: Property::new(props.firmware_missing),
377            nm_plugin_missing: Property::new(props.nm_plugin_missing),
378            device_type: Property::new(NMDeviceType::from_u32(props.device_type)),
379            available_connections: Property::new(props.available_connections),
380            physical_port_id: Property::new(props.physical_port_id),
381            mtu: Property::new(props.mtu),
382            metered: Property::new(NMMetered::from_u32(props.metered)),
383            real: Property::new(props.real),
384            ip4_connectivity: Property::new(NMConnectivityState::from_u32(props.ip4_connectivity)),
385            ip6_connectivity: Property::new(NMConnectivityState::from_u32(props.ip6_connectivity)),
386            interface_flags: Property::new(NMDeviceInterfaceFlags::from_bits_truncate(
387                props.interface_flags,
388            )),
389            hw_address: Property::new(props.hw_address),
390            ports: Property::new(props.ports),
391            // No idea what the properties for LLDP are - feel free to open a PR if you need this
392            // In the meantime, lldp_neighbors will always return an empty vec
393            lldp_neighbors: Property::new(vec![]),
394        }
395    }
396
397    /// Whether or not this device is managed by NetworkManager.
398    ///
399    /// # Errors
400    /// Returns error if the D-Bus operation fails.
401    pub async fn set_managed(&self, managed: bool) -> Result<(), Error> {
402        DeviceControls::set_managed(&self.connection, &self.object_path, managed).await
403    }
404
405    /// If TRUE, indicates the device is allowed to autoconnect.
406    ///
407    /// # Errors
408    /// Returns error if the D-Bus operation fails.
409    pub async fn set_autoconnect(&self, autoconnect: bool) -> Result<(), Error> {
410        DeviceControls::set_autoconnect(&self.connection, &self.object_path, autoconnect).await
411    }
412
413    /// Attempts to update device with new connection settings and properties.
414    ///
415    /// # Arguments
416    /// * `connection` - Optional connection settings
417    /// * `version_id` - Settings version id (0 for current)
418    /// * `flags` - Flags (none defined)
419    ///
420    /// # Errors
421    /// Returns error if the reapply operation fails.
422    pub async fn reapply(
423        &self,
424        connection_settings: HashMap<String, HashMap<String, OwnedValue>>,
425        version_id: u64,
426        flags: u32,
427    ) -> Result<(), Error> {
428        DeviceControls::reapply(
429            &self.connection,
430            &self.object_path,
431            connection_settings,
432            version_id,
433            flags,
434        )
435        .await
436    }
437
438    /// Get the currently applied connection on the device.
439    ///
440    /// # Arguments
441    /// * `flags` - Flags (none defined)
442    ///
443    /// # Returns
444    /// * Connection settings
445    /// * Version id
446    ///
447    /// # Errors
448    /// Returns error if getting the applied connection fails.
449    pub async fn get_applied_connection(&self, flags: u32) -> Result<AppliedConnection, Error> {
450        DeviceControls::get_applied_connection(&self.connection, &self.object_path, flags).await
451    }
452
453    /// Disconnects a device and prevents the device from automatically activating further connections without user intervention.
454    ///
455    /// # Errors
456    /// Returns error if the disconnect operation fails.
457    pub async fn disconnect(&self) -> Result<(), Error> {
458        DeviceControls::disconnect(&self.connection, &self.object_path).await
459    }
460
461    /// Deletes a software device from NetworkManager and removes the interface from the system.
462    ///
463    /// # Errors
464    /// Returns error if the delete operation fails.
465    pub async fn delete(&self) -> Result<(), Error> {
466        DeviceControls::delete(&self.connection, &self.object_path).await
467    }
468
469    /// Sets the managed state with optional persistence. Since: NM 1.58.
470    ///
471    /// # Errors
472    /// Returns error if the operation fails.
473    pub async fn set_managed_ext(
474        &self,
475        managed: NMDeviceManaged,
476        flags: NMDeviceManagedFlags,
477    ) -> Result<(), Error> {
478        DeviceControls::set_managed_ext(&self.connection, &self.object_path, managed, flags).await
479    }
480
481    /// Emitted when the device's state changes.
482    ///
483    /// # Errors
484    /// Returns error if D-Bus proxy creation fails.
485    pub async fn device_state_changed_signal(
486        &self,
487    ) -> Result<impl Stream<Item = DeviceStateChangedEvent>, Error> {
488        let proxy = DeviceProxy::new(&self.connection, &self.object_path).await?;
489        let stream = proxy.receive_device_state_changed().await?;
490
491        Ok(stream.filter_map(|signal| async move {
492            signal.args().ok().map(|args| DeviceStateChangedEvent {
493                new_state: NMDeviceState::from_u32(args.new_state),
494                old_state: NMDeviceState::from_u32(args.old_state),
495                reason: NMDeviceStateReason::from_u32(args.reason),
496            })
497        }))
498    }
499}