Skip to main content

wayle_network/
service.rs

1use std::sync::Arc;
2
3use derive_more::Debug;
4use futures::{Stream, StreamExt};
5use tokio_util::sync::CancellationToken;
6use tracing::{instrument, warn};
7use wayle_core::Property;
8use wayle_traits::{Reactive, ServiceMonitoring, Static};
9use zbus::{Connection, zvariant::OwnedObjectPath};
10
11use super::{
12    core::access_point::types::{AccessPointParams, LiveAccessPointParams},
13    error::Error,
14    types::connectivity::ConnectionType,
15    wifi::Wifi,
16    wired::Wired,
17};
18use crate::{
19    core::{
20        access_point::AccessPoint,
21        config::{
22            dhcp4_config::{Dhcp4Config, Dhcp4ConfigParams},
23            dhcp6_config::{Dhcp6Config, Dhcp6ConfigParams},
24            ip4_config::{Ip4Config, Ip4ConfigParams},
25            ip6_config::{Ip6Config, Ip6ConfigParams},
26        },
27        connection::{ActiveConnection, ActiveConnectionParams, LiveActiveConnectionParams},
28        device::{
29            Device, DeviceParams, LiveDeviceParams,
30            wifi::{DeviceWifi, DeviceWifiParams, LiveDeviceWifiParams},
31            wired::{DeviceWired, DeviceWiredParams, LiveDeviceWiredParams},
32        },
33        settings::{LiveSettingsParams, Settings},
34        settings_connection::{
35            ConnectionSettings, ConnectionSettingsParams, LiveConnectionSettingsParams,
36        },
37    },
38    discovery::NetworkServiceDiscovery,
39    proxy::manager::NetworkManagerProxy,
40    types::states::NMState,
41    wifi::LiveWifiParams,
42    wired::LiveWiredParams,
43};
44
45/// Entry point for network management. See [crate-level docs](crate) for usage.
46#[derive(Debug)]
47pub struct NetworkService {
48    #[debug(skip)]
49    pub(crate) zbus_connection: Connection,
50    #[debug(skip)]
51    pub(crate) cancellation_token: CancellationToken,
52    /// Connection profile management.
53    pub settings: Arc<Settings>,
54    /// WiFi device, if present (live-updated on hot-plug).
55    pub wifi: Property<Option<Arc<Wifi>>>,
56    /// Wired device, if present (live-updated on hot-plug).
57    pub wired: Property<Option<Arc<Wired>>>,
58    /// Primary connection type as reported by NetworkManager.
59    pub primary: Property<ConnectionType>,
60}
61
62impl NetworkService {
63    /// Starts the network service and initializes all components.
64    ///
65    /// Performs device discovery, creates WiFi and wired service instances
66    /// for available devices, and sets up property monitoring. Handles
67    /// the actual initialization logic for the service.
68    ///
69    /// # Errors
70    /// Returns `NetworkError::ServiceInitializationFailed` if:
71    /// - D-Bus connection fails
72    /// - Device discovery encounters errors
73    /// - Device proxy creation fails
74    #[instrument]
75    pub async fn new() -> Result<Self, Error> {
76        let connection = Connection::system().await.map_err(|err| {
77            Error::ServiceInitializationFailed(format!("D-Bus connection failed: {err}"))
78        })?;
79
80        let cancellation_token = CancellationToken::new();
81
82        let settings = Settings::get_live(LiveSettingsParams {
83            zbus_connection: &connection,
84            cancellation_token: &cancellation_token,
85        })
86        .await
87        .map_err(|err| {
88            Error::ServiceInitializationFailed(format!("cannot initialize Settings: {err}"))
89        })?;
90
91        let wifi_device_path = NetworkServiceDiscovery::wifi_device_path(&connection).await?;
92        let wired_device_path = NetworkServiceDiscovery::wired_device_path(&connection).await?;
93
94        let wifi = if let Some(path) = wifi_device_path {
95            match Wifi::get_live(LiveWifiParams {
96                connection: &connection,
97                device_path: path.clone(),
98                cancellation_token: &cancellation_token,
99                settings: settings.clone(),
100            })
101            .await
102            {
103                Ok(wifi) => Some(wifi),
104                Err(e) => {
105                    warn!(error = %e, path = %path, "cannot create WiFi service");
106                    None
107                }
108            }
109        } else {
110            None
111        };
112
113        let wired = if let Some(path) = wired_device_path {
114            match Wired::get_live(LiveWiredParams {
115                connection: &connection,
116                device_path: path.clone(),
117                cancellation_token: &cancellation_token,
118            })
119            .await
120            {
121                Ok(wired) => Some(wired),
122                Err(e) => {
123                    warn!(error = %e, path = %path, "cannot create wired service");
124                    None
125                }
126            }
127        } else {
128            None
129        };
130
131        let primary = Property::new(ConnectionType::None);
132
133        let service = Self {
134            zbus_connection: connection.clone(),
135            cancellation_token,
136            settings,
137            wifi: Property::new(wifi),
138            wired: Property::new(wired),
139            primary,
140        };
141
142        service.start_monitoring().await?;
143
144        Ok(service)
145    }
146
147    /// Objects that implement the Connection.Active interface represent an attempt to
148    /// connect to a network using the details provided by a Connection object.
149    ///
150    /// # Errors
151    /// Returns `NetworkError::ObjectNotFound` if the connection doesn't exist.
152    /// Returns `NetworkError::DbusError` if DBus operations fail.
153    #[instrument(skip(self, path), fields(path = ?path), err)]
154    pub async fn connection(&self, path: OwnedObjectPath) -> Result<ActiveConnection, Error> {
155        ActiveConnection::get(ActiveConnectionParams {
156            connection: &self.zbus_connection,
157            path,
158        })
159        .await
160    }
161
162    /// Objects that implement the Connection.Active interface represent an attempt to
163    /// connect to a network using the details provided by a Connection object.
164    /// This variant monitors the connection for changes.
165    ///
166    /// # Errors
167    /// Returns `NetworkError::ObjectNotFound` if the connection doesn't exist.
168    /// Returns `NetworkError::DbusError` if DBus operations fail.
169    #[instrument(skip(self, path), fields(path = ?path), err)]
170    pub async fn connection_monitored(
171        &self,
172        path: OwnedObjectPath,
173    ) -> Result<Arc<ActiveConnection>, Error> {
174        ActiveConnection::get_live(LiveActiveConnectionParams {
175            connection: &self.zbus_connection,
176            path,
177            cancellation_token: &self.cancellation_token,
178        })
179        .await
180    }
181
182    /// Wi-Fi Access Point.
183    ///
184    /// # Errors
185    /// Returns `NetworkError::ObjectNotFound` if the access point doesn't exist.
186    /// Returns `NetworkError::ObjectCreationFailed` if access point creation fails.
187    #[instrument(skip(self, path), fields(path = ?path), err)]
188    pub async fn access_point(&self, path: OwnedObjectPath) -> Result<AccessPoint, Error> {
189        AccessPoint::get(AccessPointParams {
190            connection: &self.zbus_connection,
191            path,
192        })
193        .await
194    }
195
196    /// Wi-Fi Access Point.
197    /// This variant monitors the access point for changes.
198    ///
199    /// # Errors
200    /// Returns `NetworkError::ObjectNotFound` if the access point doesn't exist.
201    /// Returns `NetworkError::ObjectCreationFailed` if access point creation fails.
202    #[instrument(skip(self, path), fields(path = ?path), err)]
203    pub async fn access_point_monitored(
204        &self,
205        path: OwnedObjectPath,
206    ) -> Result<Arc<AccessPoint>, Error> {
207        AccessPoint::get_live(LiveAccessPointParams {
208            connection: &self.zbus_connection,
209            path,
210            cancellation_token: &self.cancellation_token,
211        })
212        .await
213    }
214
215    /// Represents a single network connection configuration.
216    ///
217    /// # Errors
218    /// Returns `NetworkError::ObjectNotFound` if the connection profile doesn't exist.
219    /// Returns `NetworkError::DbusError` if DBus operations fail.
220    #[instrument(skip(self, path), fields(path = ?path), err)]
221    pub async fn connection_settings(
222        &self,
223        path: OwnedObjectPath,
224    ) -> Result<ConnectionSettings, Error> {
225        ConnectionSettings::get(ConnectionSettingsParams {
226            connection: &self.zbus_connection,
227            path,
228        })
229        .await
230    }
231
232    /// Represents a single network connection configuration.
233    /// This variant monitors the connection settings for changes.
234    ///
235    /// # Errors
236    /// Returns `NetworkError::ObjectNotFound` if the connection profile doesn't exist.
237    /// Returns `NetworkError::DbusError` if DBus operations fail.
238    #[instrument(skip(self, path), fields(path = ?path), err)]
239    pub async fn connection_settings_monitored(
240        &self,
241        path: OwnedObjectPath,
242    ) -> Result<Arc<ConnectionSettings>, Error> {
243        ConnectionSettings::get_live(LiveConnectionSettingsParams {
244            connection: &self.zbus_connection,
245            path,
246            cancellation_token: &self.cancellation_token,
247        })
248        .await
249    }
250
251    /// Represents a network device.
252    ///
253    /// # Errors
254    /// Returns `NetworkError::ObjectNotFound` if the device doesn't exist.
255    /// Returns `NetworkError::DbusError` if DBus operations fail.
256    #[instrument(skip(self, path), fields(path = ?path), err)]
257    pub async fn device(&self, path: OwnedObjectPath) -> Result<Device, Error> {
258        Device::get(DeviceParams {
259            connection: &self.zbus_connection,
260            object_path: path,
261        })
262        .await
263    }
264
265    /// Represents a network device.
266    /// This variant monitors the device for changes.
267    ///
268    /// # Errors
269    /// Returns `NetworkError::ObjectNotFound` if the device doesn't exist.
270    /// Returns `NetworkError::DbusError` if DBus operations fail.
271    #[instrument(skip(self, path), fields(path = ?path), err)]
272    pub async fn device_monitored(&self, path: OwnedObjectPath) -> Result<Arc<Device>, Error> {
273        Device::get_live(LiveDeviceParams {
274            connection: &self.zbus_connection,
275            object_path: path,
276            cancellation_token: &self.cancellation_token,
277        })
278        .await
279    }
280
281    /// Represents a Wi-Fi device.
282    ///
283    /// # Errors
284    /// Returns `NetworkError::ObjectNotFound` if the device doesn't exist.
285    /// Returns `NetworkError::WrongObjectType` if the device is not a WiFi device.
286    #[instrument(skip(self, path), fields(path = ?path), err)]
287    pub async fn device_wifi(&self, path: OwnedObjectPath) -> Result<DeviceWifi, Error> {
288        DeviceWifi::get(DeviceWifiParams {
289            connection: &self.zbus_connection,
290            device_path: path,
291        })
292        .await
293    }
294
295    /// Represents a Wi-Fi device.
296    /// This variant monitors the device for changes.
297    ///
298    /// # Errors
299    /// Returns `NetworkError::ObjectNotFound` if the device doesn't exist.
300    /// Returns `NetworkError::WrongObjectType` if the device is not a WiFi device.
301    #[instrument(skip(self, path), fields(path = ?path), err)]
302    pub async fn device_wifi_monitored(
303        &self,
304        path: OwnedObjectPath,
305    ) -> Result<Arc<DeviceWifi>, Error> {
306        DeviceWifi::get_live(LiveDeviceWifiParams {
307            connection: &self.zbus_connection,
308            device_path: path,
309            cancellation_token: &self.cancellation_token,
310        })
311        .await
312    }
313
314    /// Represents a wired Ethernet device.
315    ///
316    /// # Errors
317    /// Returns `NetworkError::ObjectNotFound` if the device doesn't exist.
318    /// Returns `NetworkError::WrongObjectType` if the device is not an ethernet device.
319    #[instrument(skip(self, path), fields(path = ?path), err)]
320    pub async fn device_wired(&self, path: OwnedObjectPath) -> Result<DeviceWired, Error> {
321        DeviceWired::get(DeviceWiredParams {
322            connection: &self.zbus_connection,
323            device_path: path,
324        })
325        .await
326    }
327
328    /// Represents a wired Ethernet device.
329    /// This variant monitors the device for changes.
330    ///
331    /// # Errors
332    /// Returns `NetworkError::ObjectNotFound` if the device doesn't exist.
333    /// Returns `NetworkError::WrongObjectType` if the device is not an ethernet device.
334    #[instrument(skip(self, path), fields(path = ?path), err)]
335    pub async fn device_wired_monitored(
336        &self,
337        path: OwnedObjectPath,
338    ) -> Result<Arc<DeviceWired>, Error> {
339        DeviceWired::get_live(LiveDeviceWiredParams {
340            connection: &self.zbus_connection,
341            device_path: path,
342            cancellation_token: &self.cancellation_token,
343        })
344        .await
345    }
346
347    /// IPv4 Configuration Set.
348    ///
349    /// # Errors
350    /// Returns `NetworkError::ObjectNotFound` if the configuration doesn't exist.
351    /// Returns `NetworkError::DbusError` if DBus operations fail.
352    #[instrument(skip(self, path), fields(path = ?path), err)]
353    pub async fn ip4_config(&self, path: OwnedObjectPath) -> Result<Ip4Config, Error> {
354        Ip4Config::get(Ip4ConfigParams {
355            connection: &self.zbus_connection,
356            path,
357        })
358        .await
359    }
360
361    /// IPv6 Configuration Set.
362    ///
363    /// # Errors
364    /// Returns `NetworkError::ObjectNotFound` if the configuration doesn't exist.
365    /// Returns `NetworkError::DbusError` if DBus operations fail.
366    #[instrument(skip(self, path), fields(path = ?path), err)]
367    pub async fn ip6_config(&self, path: OwnedObjectPath) -> Result<Ip6Config, Error> {
368        Ip6Config::get(Ip6ConfigParams {
369            connection: &self.zbus_connection,
370            path,
371        })
372        .await
373    }
374
375    /// DHCP4 Configuration.
376    ///
377    /// # Errors
378    /// Returns `NetworkError::ObjectNotFound` if the configuration doesn't exist.
379    /// Returns `NetworkError::DbusError` if DBus operations fail.
380    #[instrument(skip(self, path), fields(path = ?path), err)]
381    pub async fn dhcp4_config(&self, path: OwnedObjectPath) -> Result<Dhcp4Config, Error> {
382        Dhcp4Config::get(Dhcp4ConfigParams {
383            connection: &self.zbus_connection,
384            path,
385        })
386        .await
387    }
388
389    /// DHCP6 Configuration.
390    ///
391    /// # Errors
392    /// Returns `NetworkError::ObjectNotFound` if the configuration doesn't exist.
393    /// Returns `NetworkError::DbusError` if DBus operations fail.
394    #[instrument(skip(self, path), fields(path = ?path), err)]
395    pub async fn dhcp6_config(&self, path: OwnedObjectPath) -> Result<Dhcp6Config, Error> {
396        Dhcp6Config::get(Dhcp6ConfigParams {
397            connection: &self.zbus_connection,
398            path,
399        })
400        .await
401    }
402
403    /// Emitted when system authorization details change.
404    ///
405    /// # Errors
406    /// Returns error if D-Bus proxy creation fails.
407    pub async fn check_permissions_signal(&self) -> Result<impl Stream<Item = ()>, Error> {
408        let proxy = NetworkManagerProxy::new(&self.zbus_connection).await?;
409        let stream = proxy.receive_check_permissions().await?;
410
411        Ok(stream.filter_map(|_signal| async move { Some(()) }))
412    }
413
414    /// NetworkManager's state changed.
415    ///
416    /// # Errors
417    /// Returns error if D-Bus proxy creation fails.
418    pub async fn state_changed_signal(&self) -> Result<impl Stream<Item = NMState>, Error> {
419        let proxy = NetworkManagerProxy::new(&self.zbus_connection).await?;
420        let stream = proxy.receive_state_changed().await?;
421
422        Ok(stream.filter_map(|signal| async move {
423            signal.args().ok().map(|args| NMState::from_u32(args.state))
424        }))
425    }
426
427    /// A device was added to the system.
428    ///
429    /// # Errors
430    /// Returns error if D-Bus proxy creation fails.
431    pub async fn device_added_signal(&self) -> Result<impl Stream<Item = OwnedObjectPath>, Error> {
432        let proxy = NetworkManagerProxy::new(&self.zbus_connection).await?;
433        let stream = proxy.receive_device_added().await?;
434
435        Ok(stream
436            .filter_map(|signal| async move { signal.args().ok().map(|args| args.device_path) }))
437    }
438
439    /// A device was removed from the system.
440    ///
441    /// # Errors
442    /// Returns error if D-Bus proxy creation fails.
443    pub async fn device_removed_signal(
444        &self,
445    ) -> Result<impl Stream<Item = OwnedObjectPath>, Error> {
446        let proxy = NetworkManagerProxy::new(&self.zbus_connection).await?;
447        let stream = proxy.receive_device_removed().await?;
448
449        Ok(stream
450            .filter_map(|signal| async move { signal.args().ok().map(|args| args.device_path) }))
451    }
452}
453
454impl Drop for NetworkService {
455    fn drop(&mut self) {
456        self.cancellation_token.cancel();
457    }
458}