1mod saved;
5#[cfg(test)]
6mod tests;
7
8use std::cell::RefCell;
9use std::cmp::Ordering;
10use std::collections::btree_map::Entry;
11use std::collections::{BTreeMap, HashMap};
12use std::rc::Rc;
13use std::sync::Once;
14
15use crabshell_reactive::{ReadSignal, Signal};
16use crabshell_ui::Cx;
17use zbus::{Array, Connection, Message, ObjectPath, OwnedObjectPath, Variant};
18
19pub use saved::SavedConnection;
20use saved::{SAVED, SETTINGS, SETTINGS_PATH, SavedConnections};
21
22use crate::Shared;
23use crate::dbus::{self, CALL_TIMEOUT, Daemon, OBJECT_MANAGER, PROPERTIES, Props, set_changed};
24
25const NM: &str = "org.freedesktop.NetworkManager";
26const NM_PATH: &str = "/org/freedesktop/NetworkManager";
27const ROOT: &str = "/org/freedesktop";
29const DEVICE: &str = "org.freedesktop.NetworkManager.Device";
30const WIRELESS: &str = "org.freedesktop.NetworkManager.Device.Wireless";
31const WIRED: &str = "org.freedesktop.NetworkManager.Device.Wired";
32const ACCESS_POINT: &str = "org.freedesktop.NetworkManager.AccessPoint";
33const ACTIVE: &str = "org.freedesktop.NetworkManager.Connection.Active";
34
35const MAX_DEVICES: usize = 64;
38const MAX_ACCESS_POINTS: usize = 512;
40const MAX_SSID: usize = 32;
42
43const LOOPBACK: u32 = 32;
45const UNMANAGED: u32 = 10;
47const ACTIVATED: u32 = 100;
48const FAILED: u32 = 120;
49const CARRIER: u32 = 0x10000;
52const PRIVACY: u32 = 0x1;
54const KEY_MGMT_PSK: u32 = 0x100;
57const KEY_MGMT_802_1X: u32 = 0x200;
58const KEY_MGMT_SAE: u32 = 0x400;
59const KEY_MGMT_OWE: u32 = 0x800 | 0x1000;
61const KEY_MGMT_SUITE_B_192: u32 = 0x2000;
62const MAX_OBJECTS: usize = 4096;
64
65#[derive(Clone, Copy, Debug, PartialEq, Eq)]
67pub enum NetworkKind {
68 Ethernet,
70 Wifi,
72 Other,
74}
75
76#[derive(Clone, Copy, Debug, PartialEq, Eq)]
78pub enum NetworkState {
79 Connected,
81 Connecting,
84 Disconnecting,
87 Disconnected,
89 Unavailable,
91}
92
93#[derive(Clone, Copy, Debug, PartialEq, Eq)]
95pub enum Connectivity {
96 Unknown,
99 None,
101 Portal,
103 Limited,
105 Full,
107}
108
109#[derive(Clone, Copy, Debug, PartialEq, Eq)]
112pub enum ConnectionFailReason {
113 NoSecrets,
115 WifiClientFailed,
117 WifiClientDisconnected,
119 WifiAuthTimeout,
122 WifiNetworkLost,
124 Unknown,
126}
127
128#[derive(Clone, Copy, Debug, PartialEq, Eq)]
130pub enum WifiMode {
131 Unknown,
133 AdHoc,
135 Station,
137 AccessPoint,
139 Mesh,
141}
142
143#[derive(Clone, Copy, Debug, PartialEq, Eq)]
148pub enum WifiSecurity {
149 Open,
151 Wep,
153 WpaPsk,
155 WpaEap,
157 Wpa2Psk,
159 Wpa2Eap,
161 Sae,
163 Owe,
165 Wpa3SuiteB192,
167 Unknown,
169}
170
171#[derive(Clone, Debug, PartialEq)]
173pub struct NetworkDevice {
174 pub path: String,
176 pub interface: String,
178 pub kind: NetworkKind,
180 pub state: NetworkState,
182 pub connection: String,
184 pub fail_reason: Option<ConnectionFailReason>,
187 pub wifi_mode: WifiMode,
189 pub link_speed: u32,
192 pub has_link: bool,
196}
197
198impl NetworkDevice {
199 fn from_props(path: String, object: &Object, objects: &BTreeMap<String, Object>) -> Option<Self> {
200 let props = object.device.as_ref()?;
201 let connection = dbus::get::<&ObjectPath>(props, "ActiveConnection")
202 .and_then(|at| objects.get(at.as_str())?.active.as_ref())
203 .map_or(String::new(), |active| dbus::text(active, "Id"));
204 let wifi_mode = object
205 .wireless
206 .as_ref()
207 .and_then(|w| dbus::get::<u32>(w, "Mode"));
208 let wired = object.wired.as_ref();
209 Some(NetworkDevice {
210 path,
211 interface: dbus::text(props, "Interface"),
212 kind: match dbus::get::<u32>(props, "DeviceType") {
213 Some(1) => NetworkKind::Ethernet,
214 Some(2) => NetworkKind::Wifi,
215 _ => NetworkKind::Other,
216 },
217 state: match dbus::get::<u32>(props, "State") {
218 Some(ACTIVATED) => NetworkState::Connected,
219 Some(40..=90) => NetworkState::Connecting,
220 Some(110) => NetworkState::Disconnecting,
221 Some(30 | FAILED) => NetworkState::Disconnected,
222 _ => NetworkState::Unavailable,
223 },
224 connection,
225 fail_reason: object.failure,
226 wifi_mode: match wifi_mode {
227 Some(1) => WifiMode::AdHoc,
228 Some(2) => WifiMode::Station,
229 Some(3) => WifiMode::AccessPoint,
230 Some(4) => WifiMode::Mesh,
231 _ => WifiMode::Unknown,
232 },
233 link_speed: wired.and_then(|w| dbus::get(w, "Speed")).unwrap_or(0),
234 has_link: wired
235 .and_then(|wired| {
236 let flags = dbus::get::<u32>(props, "InterfaceFlags");
237 let carrier = flags.map(|flags| flags & CARRIER != 0);
238 carrier.or_else(|| dbus::get(wired, "Carrier"))
239 })
240 .unwrap_or(false),
241 })
242 }
243}
244
245#[derive(Clone, Debug, PartialEq)]
247pub struct WifiNetwork {
248 pub ssid: String,
251 pub strength: u8,
253 pub secured: bool,
256 pub security: WifiSecurity,
258 pub connected: bool,
261 pub device: String,
263 pub access_point: String,
265}
266
267#[derive(Clone)]
270pub struct Network {
271 service: Option<Rc<NetworkService>>,
272 wifi_enabled: ReadSignal<bool>,
273 connectivity: ReadSignal<Connectivity>,
274 devices: ReadSignal<Vec<NetworkDevice>>,
275 wifi_networks: ReadSignal<Vec<WifiNetwork>>,
276 saved_connections: ReadSignal<Vec<SavedConnection>>,
277}
278
279impl Network {
280 pub fn wifi_enabled(&self) -> ReadSignal<bool> {
282 self.wifi_enabled
283 }
284
285 pub fn connectivity(&self) -> ReadSignal<Connectivity> {
288 self.connectivity
289 }
290
291 pub fn devices(&self) -> ReadSignal<Vec<NetworkDevice>> {
294 self.devices
295 }
296
297 pub fn wifi_networks(&self) -> ReadSignal<Vec<WifiNetwork>> {
301 self.wifi_networks
302 }
303
304 pub fn set_wifi_enabled(&self, enabled: bool) {
306 let body = (NM, "WirelessEnabled", Variant::from(enabled));
307 self.call(NM_PATH, PROPERTIES, "Set", body);
308 }
309
310 pub fn scan(&self, device: &str) {
314 let options = HashMap::<&str, Variant<'static>>::new();
315 self.call(device, WIRELESS, "RequestScan", options);
316 }
317
318 pub fn connect(&self, network: &WifiNetwork, password: Option<&str>) {
323 let paths = (
324 ObjectPath::try_from(network.device.clone()),
325 ObjectPath::try_from(network.access_point.clone()),
326 );
327 let (Ok(device), Ok(at)) = paths else {
328 return tracing::info!(network.ssid, "no such Wi-Fi network; nothing sent");
329 };
330 let Some(password) = password else {
331 return self.activate(network, device, at);
332 };
333 let ssid = self
335 .service
336 .as_ref()
337 .and_then(|service| {
338 let state = service.state.borrow();
339 let object = state.as_ref()?.objects.get(&network.access_point)?;
340 Some(ssid(object.access_point.as_ref()?))
341 })
342 .unwrap_or_else(|| network.ssid.clone().into_bytes());
343 let wireless = HashMap::from([("ssid", Variant::from(ssid))]);
344 let security = HashMap::from([("psk", Variant::from(password.to_owned()))]);
347 let settings = HashMap::from([
348 ("802-11-wireless", wireless),
349 ("802-11-wireless-security", security),
350 ]);
351 let body = (settings, device, at);
352 self.call(NM_PATH, NM, "AddAndActivateConnection", body);
353 }
354
355 fn activate(&self, network: &WifiNetwork, device: ObjectPath<'static>, at: ObjectPath<'static>) {
358 let Some((shared, connection)) = self.bus("ActivateConnection") else {
359 return;
360 };
361 let (spawner, open) = (Rc::clone(&shared), !network.secured);
362 let ssid = network.ssid.clone().into_bytes();
363 let ssid = self
364 .service
365 .as_ref()
366 .and_then(|service| {
367 let state = service.state.borrow();
368 let object = state.as_ref()?.objects.get(&network.access_point)?;
369 Some(self::ssid(object.access_point.as_ref()?))
370 })
371 .unwrap_or(ssid);
372 spawner.spawn(async move {
373 let pick = ObjectPath::from_static_str_unchecked("/");
374 let body = (pick, &device, &at);
375 let call = connection.call_method(Some(NM), NM_PATH, Some(NM), "ActivateConnection", &body);
376 let Err(error) = shared.timeout(CALL_TIMEOUT, call).await else {
377 return;
378 };
379 if !open {
380 return tracing::warn!(%error, "no saved connection for that network");
381 }
382 let wireless = HashMap::from([("ssid", Variant::from(ssid))]);
383 let settings = HashMap::from([("802-11-wireless", wireless)]);
384 let body = (settings, &device, &at);
385 let method = "AddAndActivateConnection";
386 let call = connection.call_method(Some(NM), NM_PATH, Some(NM), method, &body);
387 if let Err(error) = shared.timeout(CALL_TIMEOUT, call).await {
388 tracing::warn!(%error, method, "NetworkManager refused it");
389 }
390 });
391 }
392
393 pub fn disconnect(&self, device: &str) {
395 self.call(device, DEVICE, "Disconnect", ());
396 }
397
398 pub fn saved_connections(&self) -> ReadSignal<Vec<SavedConnection>> {
403 let saved = self.service.as_ref().and_then(|service| {
404 let state = service.state.borrow();
405 Some(Rc::clone(&state.as_ref()?.saved))
406 });
407 if let Some(saved) = saved {
408 saved.follow();
409 }
410 self.saved_connections
411 }
412
413 pub fn forget(&self, uuid: &str) {
416 self.call_saved(uuid, "Delete");
417 }
418
419 pub fn clear_secrets(&self, uuid: &str) {
423 self.call_saved(uuid, "ClearSecrets");
424 }
425
426 fn call_saved(&self, uuid: &str, method: &'static str) {
429 let Some((shared, connection)) = self.bus(method) else {
430 return;
431 };
432 let (spawner, uuid) = (Rc::clone(&shared), uuid.to_owned());
433 spawner.spawn(async move {
434 let called = async {
435 let find = "GetConnectionByUuid";
436 let call = connection.call_method(Some(NM), SETTINGS_PATH, Some(SETTINGS), find, &uuid);
437 let reply = shared.timeout(CALL_TIMEOUT, call).await?;
438 let path = reply.body().deserialize::<OwnedObjectPath>()?;
439 let call = connection.call_method(Some(NM), path.as_str(), Some(SAVED), method, &());
440 shared.timeout(CALL_TIMEOUT, call).await.map(drop)
441 };
442 if let Err(error) = called.await {
443 tracing::warn!(%error, uuid, method, "NetworkManager refused it");
444 }
445 });
446 }
447
448 fn call<B>(&self, path: &str, interface: &'static str, method: &'static str, body: B)
451 where
452 B: serde::Serialize + zbus::DynamicType + 'static,
453 {
454 let Some((shared, connection)) = self.bus(method) else {
455 return;
456 };
457 let (spawner, path) = (Rc::clone(&shared), path.to_owned());
458 spawner.spawn(async move {
459 let call = connection.call_method(Some(NM), path.as_str(), Some(interface), method, &body);
460 if let Err(error) = shared.timeout(CALL_TIMEOUT, call).await {
461 tracing::warn!(%error, path, method, "NetworkManager refused it");
462 }
463 });
464 }
465
466 fn bus(&self, method: &str) -> Option<(Rc<Shared>, Connection)> {
469 let service = self.service.as_ref()?;
470 let state = service.state.borrow();
471 let Some(connection) = state.as_ref().and_then(|state| state.connection.clone()) else {
472 tracing::info!(method, "NetworkManager isn't running; nothing sent");
473 return None;
474 };
475 Some((Rc::clone(&service.shared), connection))
476 }
477}
478
479pub(crate) fn network(cx: &Cx) -> Network {
481 let Some(service) = cx.service::<NetworkService>() else {
482 static LOGGED: Once = Once::new();
483 LOGGED.call_once(|| {
484 tracing::warn!("cx.network() needs the services the shell provides; it stays empty");
485 });
486 return Network {
487 service: None,
488 wifi_enabled: cx.signal(false).read_only(),
489 connectivity: cx.signal(Connectivity::Unknown).read_only(),
490 devices: cx.signal(Vec::new()).read_only(),
491 wifi_networks: cx.signal(Vec::new()).read_only(),
492 saved_connections: cx.signal(Vec::new()).read_only(),
493 };
494 };
495 service.start()
496}
497
498pub(crate) struct NetworkService {
500 shared: Rc<Shared>,
501 state: RefCell<Option<State>>,
502}
503
504struct State {
505 wifi_enabled: Signal<bool>,
506 connectivity: Signal<Connectivity>,
507 devices: Signal<Vec<NetworkDevice>>,
508 wifi_networks: Signal<Vec<WifiNetwork>>,
509 saved: Rc<SavedConnections>,
511 objects: BTreeMap<String, Object>,
513 connection: Option<Connection>,
515 generation: u64,
516}
517
518impl State {
519 fn network(&self, service: &Rc<NetworkService>) -> Network {
521 Network {
522 service: Some(Rc::clone(service)),
523 wifi_enabled: self.wifi_enabled.read_only(),
524 connectivity: self.connectivity.read_only(),
525 devices: self.devices.read_only(),
526 wifi_networks: self.wifi_networks.read_only(),
527 saved_connections: self.saved.list.read_only(),
528 }
529 }
530}
531
532#[derive(Default)]
535struct Object {
536 manager: Option<Props>,
537 device: Option<Props>,
538 wireless: Option<Props>,
539 wired: Option<Props>,
540 access_point: Option<Props>,
541 active: Option<Props>,
542 failure: Option<ConnectionFailReason>,
545}
546
547impl Object {
548 fn slot(&mut self, interface: &str) -> Option<&mut Option<Props>> {
551 match interface {
552 NM => Some(&mut self.manager),
553 DEVICE => Some(&mut self.device),
554 WIRELESS => Some(&mut self.wireless),
555 WIRED => Some(&mut self.wired),
556 ACCESS_POINT => Some(&mut self.access_point),
557 ACTIVE => Some(&mut self.active),
558 _ => None,
559 }
560 }
561
562 fn kept(&self) -> bool {
563 self.manager.is_some()
564 || self.device.is_some()
565 || self.access_point.is_some()
566 || self.active.is_some()
567 }
568
569 fn note_failure(&mut self) {
572 let Some(device) = &self.device else {
573 return;
574 };
575 let reason = device.get("StateReason");
577 let reason = reason.and_then(|reason| <(u32, u32)>::try_from(&**reason).ok());
578 if let Some((FAILED, reason)) = reason {
579 self.failure = Some(fail_reason(reason));
580 }
581 if let Some(40..=ACTIVATED) = dbus::get::<u32>(device, "State") {
582 self.failure = None;
583 }
584 }
585}
586
587fn fail_reason(reason: u32) -> ConnectionFailReason {
590 match reason {
591 7 => ConnectionFailReason::NoSecrets,
592 8 => ConnectionFailReason::WifiClientDisconnected,
593 9 | 10 => ConnectionFailReason::WifiClientFailed,
595 11 => ConnectionFailReason::WifiAuthTimeout,
596 53 => ConnectionFailReason::WifiNetworkLost,
597 _ => ConnectionFailReason::Unknown,
598 }
599}
600
601fn by_number(a: &str, b: &str) -> Ordering {
604 (a.len(), a).cmp(&(b.len(), b))
605}
606
607fn ssid(props: &Props) -> Vec<u8> {
610 let bytes = dbus::get::<&Array>(props, "Ssid");
611 let bytes = bytes.iter().flat_map(|bytes| bytes.inner());
612 let bytes = bytes.map_while(|byte| match byte {
613 Variant::U8(byte) => Some(*byte),
614 _ => None,
615 });
616 bytes.take(MAX_SSID).collect()
617}
618
619fn paths<'a>(props: &'a Props, name: &str) -> impl Iterator<Item = &'a str> {
621 let paths = dbus::get::<&Array>(props, name);
622 paths
623 .into_iter()
624 .flat_map(|paths| paths.inner())
625 .filter_map(|path| match path {
626 Variant::ObjectPath(path) => Some(path.as_str()),
627 _ => None,
628 })
629}
630
631fn add(objects: &mut BTreeMap<String, Object>, path: String, interfaces: HashMap<String, Props>) {
634 if objects.len() >= MAX_OBJECTS && !objects.contains_key(&path) {
635 return tracing::debug!(path, "too many network objects; leaving one out");
636 }
637 let mut object = objects.remove(&path).unwrap_or_default();
638 for (interface, props) in interfaces {
639 if let Some(slot) = object.slot(&interface) {
640 *slot = Some(props);
641 }
642 }
643 object.note_failure();
644 if object.kept() {
645 objects.insert(path, object);
646 }
647}
648
649fn security(point: &Props) -> WifiSecurity {
654 let flags = |name| dbus::get::<u32>(point, name).unwrap_or(0);
655 let (wpa, rsn) = (flags("WpaFlags"), flags("RsnFlags"));
656 let strongest_first = [
657 (rsn, KEY_MGMT_SUITE_B_192, WifiSecurity::Wpa3SuiteB192),
658 (rsn, KEY_MGMT_SAE, WifiSecurity::Sae),
659 (rsn, KEY_MGMT_PSK, WifiSecurity::Wpa2Psk),
660 (rsn, KEY_MGMT_802_1X, WifiSecurity::Wpa2Eap),
661 (wpa, KEY_MGMT_PSK, WifiSecurity::WpaPsk),
662 (wpa, KEY_MGMT_802_1X, WifiSecurity::WpaEap),
663 (rsn, KEY_MGMT_OWE, WifiSecurity::Owe),
664 ];
665 let offered = strongest_first
666 .into_iter()
667 .find(|(flags, key, _)| flags & key != 0);
668 match offered {
669 Some((_, _, security)) => security,
670 None if wpa != 0 || rsn != 0 => WifiSecurity::Unknown,
671 None if flags("Flags") & PRIVACY != 0 => WifiSecurity::Wep,
672 None => WifiSecurity::Open,
673 }
674}
675
676fn secured(security: WifiSecurity) -> bool {
679 !matches!(
680 security,
681 WifiSecurity::Open | WifiSecurity::Owe | WifiSecurity::Unknown
682 )
683}
684
685fn read(interface: &str, name: &str) -> bool {
689 match interface {
690 NM => matches!(name, "WirelessEnabled" | "Connectivity"),
691 DEVICE => matches!(
692 name,
693 "Interface" | "DeviceType" | "State" | "StateReason" | "ActiveConnection" | "InterfaceFlags"
694 ),
695 WIRELESS => matches!(name, "AccessPoints" | "ActiveAccessPoint" | "Mode"),
696 WIRED => matches!(name, "Speed" | "Carrier"),
697 ACCESS_POINT => matches!(
698 name,
699 "Ssid" | "Strength" | "Flags" | "WpaFlags" | "RsnFlags"
700 ),
701 ACTIVE => name == "Id",
702 _ => false,
703 }
704}
705
706fn wifi_networks(objects: &BTreeMap<String, Object>) -> Vec<WifiNetwork> {
709 let mut best = BTreeMap::<(&str, Vec<u8>), WifiNetwork>::new();
710 for (path, object) in objects {
711 let (Some(device), Some(wireless)) = (&object.device, &object.wireless) else {
712 continue;
713 };
714 let on = dbus::get::<u32>(device, "State") == Some(ACTIVATED);
715 let active = dbus::get::<&ObjectPath>(wireless, "ActiveAccessPoint");
716 let active = active.map(|at| at.as_str());
717 for at in paths(wireless, "AccessPoints") {
718 let Some(point) = objects
719 .get(at)
720 .and_then(|object| object.access_point.as_ref())
721 else {
722 continue;
723 };
724 let ssid = ssid(point);
725 if ssid.iter().all(|byte| *byte == 0) {
727 continue;
728 }
729 let security = self::security(point);
730 let network = WifiNetwork {
731 ssid: String::from_utf8_lossy(&ssid).into_owned(),
732 strength: dbus::get::<u8>(point, "Strength").unwrap_or(0).min(100),
733 secured: secured(security),
734 security,
735 connected: on && active == Some(at),
736 device: path.clone(),
737 access_point: at.to_owned(),
738 };
739 match best.entry((path, ssid)) {
740 Entry::Vacant(entry) => {
741 entry.insert(network);
742 }
743 Entry::Occupied(mut entry) => {
744 let connected = entry.get().connected || network.connected;
745 if network.strength > entry.get().strength {
746 entry.insert(network);
747 }
748 entry.get_mut().connected = connected;
749 }
750 }
751 }
752 }
753 let mut networks = best.into_values().collect::<Vec<_>>();
754 networks.sort_by(|a, b| {
755 let ties = || (&a.ssid, &a.device).cmp(&(&b.ssid, &b.device));
756 b.strength.cmp(&a.strength).then_with(ties)
757 });
758 networks.truncate(MAX_ACCESS_POINTS);
759 networks
760}
761
762impl NetworkService {
763 pub(crate) fn new(shared: Rc<Shared>) -> NetworkService {
764 NetworkService {
765 shared,
766 state: RefCell::new(None),
767 }
768 }
769
770 fn start(self: &Rc<Self>) -> Network {
771 if let Some(state) = &*self.state.borrow() {
772 return state.network(self);
773 }
774 let root = &self.shared.root;
775 let state = State {
776 wifi_enabled: root.signal(false),
777 connectivity: root.signal(Connectivity::Unknown),
778 devices: root.signal(Vec::new()),
779 wifi_networks: root.signal(Vec::new()),
780 saved: SavedConnections::new(Rc::clone(&self.shared)),
781 objects: BTreeMap::new(),
782 connection: None,
783 generation: 0,
784 };
785 let network = state.network(self);
786 *self.state.borrow_mut() = Some(state);
787 match self.shared.config.system_bus.clone() {
788 Some(address) => {
789 let follow = dbus::follow(Rc::clone(&self.shared), Rc::clone(self), address);
790 self.shared.spawn(follow);
791 }
792 None => tracing::info!("no system bus, so there is no networking"),
793 }
794 network
795 }
796
797 fn publish(&self) {
801 let shown = self.state.borrow().as_ref().map(|state| {
803 let objects = &state.objects;
804 let manager = objects
805 .get(NM_PATH)
806 .and_then(|object| object.manager.as_ref());
807 let enabled = manager.and_then(|props| dbus::get(props, "WirelessEnabled"));
808 let connectivity = match manager.and_then(|props| dbus::get::<u32>(props, "Connectivity")) {
809 Some(1) => Connectivity::None,
810 Some(2) => Connectivity::Portal,
811 Some(3) => Connectivity::Limited,
812 Some(4) => Connectivity::Full,
813 _ => Connectivity::Unknown,
814 };
815 let mut devices = objects
816 .iter()
817 .filter_map(|(path, object)| {
818 let props = object.device.as_ref()?;
819 let kind = dbus::get::<u32>(props, "DeviceType");
820 let state = dbus::get::<u32>(props, "State");
821 if kind == Some(LOOPBACK) || state == Some(UNMANAGED) {
822 return None;
823 }
824 NetworkDevice::from_props(path.clone(), object, objects)
825 })
826 .collect::<Vec<_>>();
827 devices.sort_by(|a, b| by_number(&a.path, &b.path));
828 devices.truncate(MAX_DEVICES);
829 let shown = (
830 enabled.unwrap_or(false),
831 connectivity,
832 devices,
833 wifi_networks(objects),
834 );
835 let signals = (
836 state.wifi_enabled,
837 state.connectivity,
838 state.devices,
839 state.wifi_networks,
840 );
841 (signals, shown)
842 });
843 if let Some((signals, shown)) = shown {
844 set_changed(signals.0, shown.0);
845 set_changed(signals.1, shown.1);
846 set_changed(signals.2, shown.2);
847 set_changed(signals.3, shown.3);
848 }
849 }
850}
851
852impl Daemon for NetworkService {
853 const NAME: &'static str = NM;
854
855 fn rules() -> Vec<String> {
859 let mut rules = vec![format!(
860 "type='signal',sender='{NM}',path='{ROOT}',interface='{OBJECT_MANAGER}'"
861 )];
862 rules.extend(
863 [NM, DEVICE, WIRELESS, WIRED, ACCESS_POINT, ACTIVE].map(|interface| {
864 format!(
865 "type='signal',sender='{NM}',interface='{PROPERTIES}',member='PropertiesChanged',\
866 arg0='{interface}'"
867 )
868 }),
869 );
870 rules
871 }
872
873 fn alive(&self) -> bool {
874 let state = self.state.borrow();
875 state
876 .as_ref()
877 .is_some_and(|state| state.devices.try_with(|_| ()).is_some())
878 }
879
880 fn clear(&self) -> u64 {
881 let generation = {
882 let mut state = self.state.borrow_mut();
883 let Some(state) = state.as_mut() else {
884 return 0;
885 };
886 state.objects.clear();
887 state.connection = None;
888 state.generation += 1;
889 state.generation
890 };
891 self.publish();
892 generation
893 }
894
895 async fn read(self: Rc<Self>, connection: Connection, generation: u64) {
896 let call = connection.call_method(
897 Some(NM),
898 ROOT,
899 Some(OBJECT_MANAGER),
900 "GetManagedObjects",
901 &(),
902 );
903 let read = async {
904 let reply = self.shared.timeout(CALL_TIMEOUT, call).await?;
905 reply
906 .body()
907 .deserialize::<HashMap<OwnedObjectPath, HashMap<String, Props>>>()
908 };
909 let managed = match read.await {
910 Ok(managed) => managed,
911 Err(error) => return tracing::warn!(%error, "couldn't read NetworkManager"),
912 };
913 let mut managed = managed
915 .into_iter()
916 .map(|(path, interfaces)| (path.as_str().to_owned(), interfaces))
917 .collect::<Vec<_>>();
918 managed.sort_by(|(a, _), (b, _)| by_number(a, b));
919 {
920 let mut state = self.state.borrow_mut();
921 let Some(state) = state
922 .as_mut()
923 .filter(|state| state.generation == generation)
924 else {
925 return;
926 };
927 state.objects.clear();
928 for (path, interfaces) in managed {
929 add(&mut state.objects, path, interfaces);
930 }
931 state.connection = Some(connection);
932 }
933 self.publish();
934 }
935
936 fn signal(self: &Rc<Self>, _: &Connection, message: &Message) {
937 if let Some((path, interface, changed, dropped)) = dbus::properties_changed(message) {
938 {
939 let mut state = self.state.borrow_mut();
940 let object = state
941 .as_mut()
942 .and_then(|state| state.objects.get_mut(&path));
943 let Some(object) = object else {
944 return;
945 };
946 let Some(props) = object.slot(&interface).and_then(Option::as_mut) else {
947 return;
948 };
949 let shown = changed
950 .keys()
951 .chain(&dropped)
952 .any(|name| read(&interface, name));
953 dbus::apply(props, changed, dropped);
954 object.note_failure();
955 if !shown {
956 return;
957 }
958 }
959 return self.publish();
960 }
961 let header = message.header();
962 if header.interface().map(|interface| interface.as_str()) != Some(OBJECT_MANAGER) {
963 return;
964 }
965 let body = message.body();
966 {
967 let mut state = self.state.borrow_mut();
968 let Some(state) = state.as_mut() else {
969 return;
970 };
971 match header.member().map(|member| member.as_str()) {
972 Some("InterfacesAdded") => {
973 let Ok((path, interfaces)) =
974 body.deserialize::<(OwnedObjectPath, HashMap<String, Props>)>()
975 else {
976 return;
977 };
978 add(&mut state.objects, path.as_str().to_owned(), interfaces);
979 }
980 Some("InterfacesRemoved") => {
981 let Ok((path, interfaces)) = body.deserialize::<(OwnedObjectPath, Vec<String>)>() else {
982 return;
983 };
984 let Some(object) = state.objects.get_mut(path.as_str()) else {
985 return;
986 };
987 for interface in interfaces {
988 if let Some(slot) = object.slot(&interface) {
989 *slot = None;
990 }
991 }
992 if !object.kept() {
993 state.objects.remove(path.as_str());
994 }
995 }
996 _ => return,
997 }
998 }
999 self.publish();
1000 }
1001}