1use std::collections::HashMap;
2use std::sync::mpsc;
3use tauri::{plugin::PluginApi, AppHandle, Runtime};
4use uuid::Uuid;
5use zbus::names::InterfaceName;
6use zbus::zvariant::Value;
7
8use crate::error::Result;
9use crate::models::*;
10use crate::nm_helpers::NetworkManagerHelpers;
11
12impl<R: Runtime> VSKNetworkManager<'static, R> {
13 fn vpn_type_from_service_type(service_type: &str) -> VpnType {
14 match service_type {
15 "org.freedesktop.NetworkManager.openvpn" => VpnType::OpenVpn,
16 "org.freedesktop.NetworkManager.wireguard" => VpnType::WireGuard,
17 "org.freedesktop.NetworkManager.l2tp" => VpnType::L2tp,
18 "org.freedesktop.NetworkManager.pptp" => VpnType::Pptp,
19 "org.freedesktop.NetworkManager.sstp" => VpnType::Sstp,
20 "org.freedesktop.NetworkManager.strongswan" => VpnType::Ikev2,
21 "org.freedesktop.NetworkManager.fortisslvpn" => VpnType::Fortisslvpn,
22 "org.freedesktop.NetworkManager.openconnect" => VpnType::OpenConnect,
23 _ => VpnType::Generic,
24 }
25 }
26
27 fn service_type_from_vpn_type(vpn_type: &VpnType) -> &'static str {
28 match vpn_type {
29 VpnType::OpenVpn => "org.freedesktop.NetworkManager.openvpn",
30 VpnType::WireGuard => "org.freedesktop.NetworkManager.wireguard",
31 VpnType::L2tp => "org.freedesktop.NetworkManager.l2tp",
32 VpnType::Pptp => "org.freedesktop.NetworkManager.pptp",
33 VpnType::Sstp => "org.freedesktop.NetworkManager.sstp",
34 VpnType::Ikev2 => "org.freedesktop.NetworkManager.strongswan",
35 VpnType::Fortisslvpn => "org.freedesktop.NetworkManager.fortisslvpn",
36 VpnType::OpenConnect => "org.freedesktop.NetworkManager.openconnect",
37 VpnType::Generic => "org.freedesktop.NetworkManager.vpnc",
38 }
39 }
40
41 fn vpn_state_from_active_state(state: u32) -> VpnConnectionState {
42 match state {
43 1 => VpnConnectionState::Connecting,
44 2 => VpnConnectionState::Connected,
45 3 => VpnConnectionState::Disconnecting,
46 4 => VpnConnectionState::Disconnected,
47 _ => VpnConnectionState::Unknown,
48 }
49 }
50
51 fn extract_string_from_dict(
52 dict: &HashMap<String, zbus::zvariant::OwnedValue>,
53 key: &str,
54 ) -> Option<String> {
55 let value = dict.get(key)?.to_owned();
56 <zbus::zvariant::Value<'_> as Clone>::clone(&value)
57 .downcast::<String>()
58 }
59
60 fn extract_bool_from_dict(
61 dict: &HashMap<String, zbus::zvariant::OwnedValue>,
62 key: &str,
63 ) -> Option<bool> {
64 let value = dict.get(key)?.to_owned();
65 <zbus::zvariant::Value<'_> as Clone>::clone(&value)
66 .downcast::<bool>()
67 }
68
69 fn string_map_from_section(
70 settings: &HashMap<String, zbus::zvariant::OwnedValue>,
71 section_name: &str,
72 ) -> HashMap<String, String> {
73 let mut out = HashMap::new();
74 let section = match settings.get(section_name) {
75 Some(v) => v.to_owned(),
76 None => return out,
77 };
78 let dict = match <zbus::zvariant::Value<'_> as Clone>::clone(§ion)
79 .downcast::<HashMap<String, zbus::zvariant::OwnedValue>>()
80 {
81 Some(d) => d,
82 None => return out,
83 };
84
85 for (k, v) in dict {
86 let value = <zbus::zvariant::Value<'_> as Clone>::clone(&v);
87 if let Some(s) = value.downcast::<String>() {
88 out.insert(k, s);
89 }
90 }
91 out
92 }
93
94 fn list_connection_paths(&self) -> Result<Vec<zbus::zvariant::OwnedObjectPath>> {
95 let settings_proxy = zbus::blocking::Proxy::new(
96 &self.connection,
97 "org.freedesktop.NetworkManager",
98 "/org/freedesktop/NetworkManager/Settings",
99 "org.freedesktop.NetworkManager.Settings",
100 )?;
101
102 let connections: Vec<zbus::zvariant::OwnedObjectPath> =
103 settings_proxy.call("ListConnections", &())?;
104 Ok(connections)
105 }
106
107 fn get_connection_settings(
108 &self,
109 conn_path: &zbus::zvariant::OwnedObjectPath,
110 ) -> Result<HashMap<String, zbus::zvariant::OwnedValue>> {
111 let conn_proxy = zbus::blocking::Proxy::new(
112 &self.connection,
113 "org.freedesktop.NetworkManager",
114 conn_path.as_str(),
115 "org.freedesktop.NetworkManager.Settings.Connection",
116 )?;
117
118 let settings: HashMap<String, zbus::zvariant::OwnedValue> =
119 conn_proxy.call("GetSettings", &())?;
120 Ok(settings)
121 }
122
123 fn find_connection_path_by_uuid(
124 &self,
125 uuid: &str,
126 ) -> Result<zbus::zvariant::OwnedObjectPath> {
127 let connections = self.list_connection_paths()?;
128
129 for conn_path in connections {
130 let settings = self.get_connection_settings(&conn_path)?;
131 let connection_section = match settings.get("connection") {
132 Some(v) => v.to_owned(),
133 None => continue,
134 };
135 let dict = match <zbus::zvariant::Value<'_> as Clone>::clone(&connection_section)
136 .downcast::<HashMap<String, zbus::zvariant::OwnedValue>>()
137 {
138 Some(d) => d,
139 None => continue,
140 };
141
142 if let Some(conn_uuid) = Self::extract_string_from_dict(&dict, "uuid") {
143 if conn_uuid == uuid {
144 return Ok(conn_path);
145 }
146 }
147 }
148
149 Err(crate::error::NetworkError::VpnProfileNotFound(uuid.to_string()))
150 }
151
152 fn vpn_profile_from_settings(
153 &self,
154 settings: &HashMap<String, zbus::zvariant::OwnedValue>,
155 ) -> Option<VpnProfile> {
156 let connection_section = settings.get("connection")?.to_owned();
157 let connection_dict = <zbus::zvariant::Value<'_> as Clone>::clone(&connection_section)
158 .downcast::<HashMap<String, zbus::zvariant::OwnedValue>>()?;
159
160 let conn_type = Self::extract_string_from_dict(&connection_dict, "type")?;
161 if conn_type != "vpn" {
162 return None;
163 }
164
165 let uuid = Self::extract_string_from_dict(&connection_dict, "uuid")?;
166 let id = Self::extract_string_from_dict(&connection_dict, "id")
167 .unwrap_or_else(|| uuid.clone());
168 let interface_name = Self::extract_string_from_dict(&connection_dict, "interface-name");
169 let autoconnect = Self::extract_bool_from_dict(&connection_dict, "autoconnect")
170 .unwrap_or(false);
171
172 let vpn_settings = Self::string_map_from_section(settings, "vpn");
173 let service_type = vpn_settings
174 .get("service-type")
175 .map(|s| s.as_str())
176 .unwrap_or("unknown");
177
178 Some(VpnProfile {
179 id,
180 uuid,
181 vpn_type: Self::vpn_type_from_service_type(service_type),
182 interface_name,
183 autoconnect,
184 editable: true,
185 last_error: None,
186 })
187 }
188
189 fn get_wifi_icon(strength: u8) -> String {
191 match strength {
192 0..=25 => "network-wireless-signal-weak-symbolic".to_string(),
193 26..=50 => "network-wireless-signal-ok-symbolic".to_string(),
194 51..=75 => "network-wireless-signal-good-symbolic".to_string(),
195 76..=100 => "network-wireless-signal-excellent-symbolic".to_string(),
196 _ => "network-wireless-signal-none-symbolic".to_string(),
197 }
198 }
199
200 fn get_wired_icon(is_connected: bool) -> String {
201 if is_connected {
202 "network-wired-symbolic".to_string()
203 } else {
204 "network-offline-symbolic".to_string()
205 }
206 }
207
208 pub async fn new(app: AppHandle<R>) -> Result<Self> {
210 let connection = zbus::blocking::Connection::system()?;
211 let proxy = zbus::blocking::fdo::PropertiesProxy::builder(&connection)
212 .destination("org.freedesktop.NetworkManager")?
213 .path("/org/freedesktop/NetworkManager")?
214 .build()?;
215
216 Ok(Self {
217 connection,
218 proxy,
219 app,
220 })
221 }
222
223 pub fn get_current_network_state(&self) -> Result<NetworkInfo> {
224 let active_connections_variant = self.proxy.get(
226 InterfaceName::from_static_str_unchecked("org.freedesktop.NetworkManager"),
227 "ActiveConnections",
228 )?;
229
230 match active_connections_variant.downcast_ref() {
232 Some(Value::Array(arr)) if !arr.is_empty() => {
233 match arr[0] {
235 zbus::zvariant::Value::ObjectPath(ref path) => {
236 let properties_proxy =
239 zbus::blocking::fdo::PropertiesProxy::builder(&self.connection)
240 .destination("org.freedesktop.NetworkManager")?
241 .path(path)?
242 .build()?;
243
244 let devices_variant = properties_proxy.get(
245 InterfaceName::from_static_str_unchecked(
246 "org.freedesktop.NetworkManager.Connection.Active",
247 ),
248 "Devices",
249 )?;
250
251 let device_path = match devices_variant.downcast_ref() {
253 Some(Value::Array(device_arr)) if !device_arr.is_empty() => {
254 match device_arr[0] {
255 zbus::zvariant::Value::ObjectPath(ref dev_path) => {
256 dev_path.clone()
257 }
258 _ => return Ok(NetworkInfo::default()),
259 }
260 }
261 _ => return Ok(NetworkInfo::default()),
262 };
263
264 let device_properties_proxy =
267 zbus::blocking::fdo::PropertiesProxy::builder(&self.connection)
268 .destination("org.freedesktop.NetworkManager")?
269 .path(&device_path)?
270 .build()?;
271
272 let connection_type = device_properties_proxy.get(
273 InterfaceName::from_static_str_unchecked(
274 "org.freedesktop.NetworkManager.Device",
275 ),
276 "DeviceType",
277 )?;
278
279 let state_variant = properties_proxy.get(
280 InterfaceName::from_static_str_unchecked(
281 "org.freedesktop.NetworkManager.Connection.Active",
282 ),
283 "State",
284 )?;
285
286 let is_connected = match state_variant.downcast_ref() {
287 Some(zbus::zvariant::Value::U32(state)) => *state == 2, _ => false,
289 };
290
291 let connection_type_str = match connection_type.downcast_ref() {
293 Some(zbus::zvariant::Value::U32(device_type)) => match device_type {
294 1 => "Ethernet".to_string(),
295 2 => "WiFi".to_string(),
296 _ => "Unknown".to_string(),
297 },
298 _ => "Unknown".to_string(),
299 };
300
301 let mut network_info = NetworkInfo {
303 name: "Unknown".to_string(),
304 ssid: "Unknown".to_string(),
305 connection_type: connection_type_str.clone(),
306 icon: "network-offline-symbolic".to_string(),
307 ip_address: "0.0.0.0".to_string(),
308 mac_address: "00:00:00:00:00:00".to_string(),
309 signal_strength: 0,
310 security_type: WiFiSecurityType::None,
311 is_connected: is_connected && NetworkManagerHelpers::has_internet_connectivity(&self.proxy)?,
312 };
313
314 let hw_address_variant = device_properties_proxy.get(
315 InterfaceName::from_static_str_unchecked(
316 "org.freedesktop.NetworkManager.Device",
317 ),
318 "HwAddress",
319 )?;
320
321 network_info.mac_address = match hw_address_variant.downcast_ref() {
322 Some(zbus::zvariant::Value::Str(s)) => s.to_string(),
323 _ => "00:00:00:00:00:00".to_string(),
324 };
325
326 if connection_type_str == "WiFi" {
328 let wireless_properties_proxy =
331 zbus::blocking::fdo::PropertiesProxy::builder(&self.connection)
332 .destination("org.freedesktop.NetworkManager")?
333 .path(&device_path)?
334 .build()?;
335
336 let active_ap_path = wireless_properties_proxy.get(
337 InterfaceName::from_static_str_unchecked(
338 "org.freedesktop.NetworkManager.Device.Wireless",
339 ),
340 "ActiveAccessPoint",
341 )?;
342
343 if let Some(zbus::zvariant::Value::ObjectPath(ap_path)) =
344 active_ap_path.downcast_ref()
345 {
346 let _ap_proxy = zbus::blocking::Proxy::new(
347 &self.connection,
348 "org.freedesktop.NetworkManager",
349 ap_path,
350 "org.freedesktop.NetworkManager.AccessPoint",
351 )?;
352
353 let ap_properties_proxy =
356 zbus::blocking::fdo::PropertiesProxy::builder(&self.connection)
357 .destination("org.freedesktop.NetworkManager")?
358 .path(ap_path)?
359 .build()?;
360
361 let ssid_variant = ap_properties_proxy.get(
362 InterfaceName::from_static_str_unchecked(
363 "org.freedesktop.NetworkManager.AccessPoint",
364 ),
365 "Ssid",
366 )?;
367
368 network_info.ssid = match ssid_variant.downcast_ref() {
369 Some(zbus::zvariant::Value::Array(ssid_bytes)) => {
370 let bytes: Vec<u8> = ssid_bytes
372 .iter()
373 .filter_map(|v| {
374 if let zbus::zvariant::Value::U8(b) = v {
375 Some(*b)
376 } else {
377 None
378 }
379 })
380 .collect();
381
382 String::from_utf8_lossy(&bytes).to_string()
383 }
384 _ => "Unknown".to_string(),
385 };
386 network_info.name = network_info.ssid.clone();
387
388 let strength_variant = ap_properties_proxy.get(
390 InterfaceName::from_static_str_unchecked(
391 "org.freedesktop.NetworkManager.AccessPoint",
392 ),
393 "Strength",
394 )?;
395
396 network_info.signal_strength = match strength_variant.downcast_ref()
397 {
398 Some(zbus::zvariant::Value::U8(s)) => *s,
399 _ => 0,
400 };
401
402 network_info.icon =
404 Self::get_wifi_icon(network_info.signal_strength);
405
406 network_info.security_type = NetworkManagerHelpers::detect_security_type(&ap_properties_proxy)?;
408 }
409 } else {
410 network_info.icon = Self::get_wired_icon(network_info.is_connected);
412 }
413 let ip4_config_path = device_properties_proxy.get(
415 InterfaceName::from_static_str_unchecked(
416 "org.freedesktop.NetworkManager.Device",
417 ),
418 "Ip4Config",
419 )?;
420
421 if let Some(zbus::zvariant::Value::ObjectPath(config_path)) =
423 ip4_config_path.downcast_ref()
424 {
425 let ip_config_properties_proxy =
427 zbus::blocking::fdo::PropertiesProxy::builder(&self.connection)
428 .destination("org.freedesktop.NetworkManager")?
429 .path(config_path)?
430 .build()?;
431
432 let addresses_variant = ip_config_properties_proxy.get(
433 InterfaceName::from_static_str_unchecked(
434 "org.freedesktop.NetworkManager.IP4Config",
435 ),
436 "Addresses",
437 )?;
438
439 if let Some(Value::Array(addr_arr)) = addresses_variant.downcast_ref() {
440 if let Some(Value::Array(ip_tuple)) = addr_arr.first() {
441 if ip_tuple.len() >= 1 {
442 if let Value::U32(ip_int) = &ip_tuple[0] {
443 use std::net::Ipv4Addr;
444 network_info.ip_address =
445 Ipv4Addr::from((*ip_int).to_be()).to_string();
446 }
447 }
448 }
449 }
450 }
451
452 Ok(network_info)
453 }
454 _ => Ok(NetworkInfo::default()),
455 }
456 }
457 _ => Ok(NetworkInfo::default()),
458 }
459 }
460
461 pub fn list_wifi_networks(&self) -> Result<Vec<NetworkInfo>> {
463 let devices_variant = self.proxy.get(
465 InterfaceName::from_static_str_unchecked("org.freedesktop.NetworkManager"),
466 "Devices",
467 )?;
468
469 let mut networks = Vec::new();
470 let current_network = self.get_current_network_state()?;
471
472 if let Some(zbus::zvariant::Value::Array(devices)) = devices_variant.downcast_ref() {
473 let device_values = devices.get();
475 for device in device_values {
476 if let zbus::zvariant::Value::ObjectPath(ref device_path) = device {
477 let device_props =
479 zbus::blocking::fdo::PropertiesProxy::builder(&self.connection)
480 .destination("org.freedesktop.NetworkManager")?
481 .path(device_path)?
482 .build()?;
483
484 let device_type_variant = device_props.get(
486 InterfaceName::from_static_str_unchecked(
487 "org.freedesktop.NetworkManager.Device",
488 ),
489 "DeviceType",
490 )?;
491
492 if let Some(zbus::zvariant::Value::U32(device_type)) =
494 device_type_variant.downcast_ref()
495 {
496 if device_type == &2u32 {
497 let mac_address = match device_props
498 .get(
499 InterfaceName::from_static_str_unchecked(
500 "org.freedesktop.NetworkManager.Device",
501 ),
502 "HwAddress",
503 )?
504 .downcast_ref()
505 {
506 Some(zbus::zvariant::Value::Str(s)) => s.to_string(),
507 _ => "00:00:00:00:00:00".to_string(),
508 };
509
510 let wireless_props =
512 zbus::blocking::fdo::PropertiesProxy::builder(&self.connection)
513 .destination("org.freedesktop.NetworkManager")?
514 .path(device_path)?
515 .build()?;
516
517 let access_points_variant = wireless_props.get(
518 InterfaceName::from_static_str_unchecked(
519 "org.freedesktop.NetworkManager.Device.Wireless",
520 ),
521 "AccessPoints",
522 )?;
523
524 if let Some(zbus::zvariant::Value::Array(aps)) =
525 access_points_variant.downcast_ref()
526 {
527 let ap_values = aps.get();
529 for ap in ap_values {
530 if let zbus::zvariant::Value::ObjectPath(ref ap_path) = ap {
531 let ap_props = zbus::blocking::fdo::PropertiesProxy::builder(
532 &self.connection,
533 )
534 .destination("org.freedesktop.NetworkManager")?
535 .path(ap_path)?
536 .build()?;
537
538 let ssid_variant = ap_props.get(
540 InterfaceName::from_static_str_unchecked(
541 "org.freedesktop.NetworkManager.AccessPoint",
542 ),
543 "Ssid",
544 )?;
545
546 let ssid = match ssid_variant.downcast_ref() {
547 Some(zbus::zvariant::Value::Array(ssid_bytes)) => {
548 let bytes: Vec<u8> = ssid_bytes
550 .iter()
551 .filter_map(|v| {
552 if let zbus::zvariant::Value::U8(b) = v {
553 Some(*b)
554 } else {
555 None
556 }
557 })
558 .collect();
559
560 String::from_utf8_lossy(&bytes).to_string()
561 }
562 _ => "Unknown".to_string(),
563 };
564
565 let strength_variant = ap_props.get(
567 InterfaceName::from_static_str_unchecked(
568 "org.freedesktop.NetworkManager.AccessPoint",
569 ),
570 "Strength",
571 )?;
572
573 let strength = match strength_variant.downcast_ref() {
574 Some(zbus::zvariant::Value::U8(s)) => *s,
575 _ => 0,
576 };
577
578 let security_type = NetworkManagerHelpers::detect_security_type(&ap_props)?;
580
581 let is_connected = current_network.ssid == ssid;
582
583 let network_info = NetworkInfo {
584 name: ssid.clone(),
585 ssid,
586 connection_type: "wifi".to_string(),
587 icon: Self::get_wifi_icon(strength),
588 ip_address: if is_connected {
589 current_network.ip_address.clone()
590 } else {
591 "0.0.0.0".to_string()
592 },
593 mac_address: mac_address.clone(),
594 signal_strength: strength,
595 security_type,
596 is_connected,
597 };
598
599 if !networks.iter().any(|n: &NetworkInfo| n.ssid == network_info.ssid) {
600 networks.push(network_info);
601 }
602 }
603 }
604 }
605 }
606 }
607 }
608 }
609 }
610
611 networks.sort_by(|a, b| b.signal_strength.cmp(&a.signal_strength));
613
614 Ok(networks)
615 }
616
617 pub fn rescan_wifi(&self) -> Result<Vec<NetworkInfo>> {
619 let devices_variant = self.proxy.get(
620 InterfaceName::from_static_str_unchecked("org.freedesktop.NetworkManager"),
621 "Devices",
622 )?;
623
624 let mut wifi_device_found = false;
625 let mut requested_scan = false;
626
627 if let Some(zbus::zvariant::Value::Array(devices)) = devices_variant.downcast_ref() {
628 for device in devices.get() {
629 if let zbus::zvariant::Value::ObjectPath(ref device_path) = device {
630 let device_props = zbus::blocking::fdo::PropertiesProxy::builder(&self.connection)
631 .destination("org.freedesktop.NetworkManager")?
632 .path(device_path)?
633 .build()?;
634
635 let device_type_variant = device_props.get(
636 InterfaceName::from_static_str_unchecked("org.freedesktop.NetworkManager.Device"),
637 "DeviceType",
638 )?;
639
640 if let Some(zbus::zvariant::Value::U32(device_type)) = device_type_variant.downcast_ref() {
641 if *device_type == 2 {
642 wifi_device_found = true;
643
644 let wireless_proxy = zbus::blocking::Proxy::new(
645 &self.connection,
646 "org.freedesktop.NetworkManager",
647 device_path.as_str(),
648 "org.freedesktop.NetworkManager.Device.Wireless",
649 )?;
650
651 let options: HashMap<String, zbus::zvariant::OwnedValue> = HashMap::new();
652 if wireless_proxy.call::<_, _, ()>("RequestScan", &(options,)).is_ok() {
653 requested_scan = true;
654 }
655 }
656 }
657 }
658 }
659 }
660
661 if !wifi_device_found {
662 return Err(crate::error::NetworkError::OperationError(
663 "No wireless device available for scanning".to_string(),
664 ));
665 }
666
667 if !requested_scan {
668 return Err(crate::error::NetworkError::OperationError(
669 "Failed to request WiFi scan on available wireless devices".to_string(),
670 ));
671 }
672
673 self.list_wifi_networks()
674 }
675
676 pub fn connect_to_wifi(&self, config: WiFiConnectionConfig) -> Result<()> {
678 log::debug!("connect_to_wifi called: ssid='{}' security={:?} username={:?}",
680 config.ssid, config.security_type, config.username);
681
682 let mut connection_settings = HashMap::new();
684 let mut wifi_settings = HashMap::new();
685 let mut security_settings = HashMap::new();
686
687 let mut connection = HashMap::new();
689 connection.insert("id".to_string(), Value::from(config.ssid.clone()));
690 connection.insert("type".to_string(), Value::from("802-11-wireless"));
691 connection_settings.insert("connection".to_string(), connection);
692
693 wifi_settings.insert("ssid".to_string(), Value::from(config.ssid.clone()));
695 wifi_settings.insert("mode".to_string(), Value::from("infrastructure"));
696
697 match config.security_type {
699 WiFiSecurityType::None => {
700 }
702 WiFiSecurityType::Wep => {
703 security_settings.insert("key-mgmt".to_string(), Value::from("none"));
704 if let Some(password) = config.password.clone() {
705 security_settings.insert("wep-key0".to_string(), Value::from(password));
706 }
707 }
708 WiFiSecurityType::WpaPsk => {
709 security_settings.insert("key-mgmt".to_string(), Value::from("wpa-psk"));
710 if let Some(password) = config.password.clone() {
711 security_settings.insert("psk".to_string(), Value::from(password));
712 }
713 }
714 WiFiSecurityType::WpaEap => {
715 security_settings.insert("key-mgmt".to_string(), Value::from("wpa-eap"));
716 if let Some(password) = config.password.clone() {
717 security_settings.insert("password".to_string(), Value::from(password));
718 }
719 if let Some(username) = config.username.clone() {
720 security_settings.insert("identity".to_string(), Value::from(username));
721 }
722 }
723 WiFiSecurityType::Wpa2Psk => {
724 security_settings.insert("key-mgmt".to_string(), Value::from("wpa-psk"));
725 security_settings.insert("proto".to_string(), Value::from("rsn"));
726 if let Some(password) = config.password.clone() {
727 security_settings.insert("psk".to_string(), Value::from(password));
728 }
729 }
730 WiFiSecurityType::Wpa3Psk => {
731 security_settings.insert("key-mgmt".to_string(), Value::from("sae"));
732 if let Some(password) = config.password.clone() {
733 security_settings.insert("psk".to_string(), Value::from(password));
734 }
735 }
736 }
737
738 connection_settings.insert("802-11-wireless".to_string(), wifi_settings);
739 connection_settings.insert("802-11-wireless-security".to_string(), security_settings);
740
741 log::trace!("connection_settings: {:#?}", connection_settings);
743
744 let nm_proxy = zbus::blocking::Proxy::new(
746 &self.connection,
747 "org.freedesktop.NetworkManager",
748 "/org/freedesktop/NetworkManager",
749 "org.freedesktop.NetworkManager",
750 )?;
751
752 let call_result: zbus::Result<(zbus::zvariant::OwnedObjectPath, zbus::zvariant::OwnedObjectPath)> = nm_proxy.call("AddAndActivateConnection", &(connection_settings, "/", "/"));
754
755 match call_result {
756 Ok((conn_path, active_path)) => {
757 log::info!(
758 "AddAndActivateConnection succeeded for ssid='{}' conn='{}' active='{}'",
759 config.ssid,
760 conn_path.as_str(),
761 active_path.as_str()
762 );
763 }
764 Err(e) => {
765 log::error!(
766 "AddAndActivateConnection failed for ssid='{}': {:?}",
767 config.ssid,
768 e
769 );
770 return Err(e.into());
771 }
772 }
773
774 log::debug!("connect_to_wifi finished for ssid='{}'", config.ssid);
775
776 Ok(())
777 }
778
779 pub fn toggle_network_state(&self, enabled: bool) -> Result<bool> {
781 let nm_proxy = zbus::blocking::Proxy::new(
782 &self.connection,
783 "org.freedesktop.NetworkManager",
784 "/org/freedesktop/NetworkManager",
785 "org.freedesktop.NetworkManager",
786 )?;
787
788 nm_proxy.set_property("NetworkingEnabled", enabled)?;
789
790 let current_state: bool = nm_proxy.get_property("NetworkingEnabled")?;
791 Ok(current_state)
792 }
793
794 pub fn get_wireless_enabled(&self) -> Result<bool> {
796 let nm_proxy = zbus::blocking::Proxy::new(
797 &self.connection,
798 "org.freedesktop.NetworkManager",
799 "/org/freedesktop/NetworkManager",
800 "org.freedesktop.NetworkManager",
801 )?;
802 Ok(nm_proxy.get_property("WirelessEnabled")?)
803 }
804
805 pub fn set_wireless_enabled(&self, enabled: bool) -> Result<()> {
807 let nm_proxy = zbus::blocking::Proxy::new(
808 &self.connection,
809 "org.freedesktop.NetworkManager",
810 "/org/freedesktop/NetworkManager",
811 "org.freedesktop.NetworkManager",
812 )?;
813 nm_proxy.set_property("WirelessEnabled", enabled)?;
814 Ok(())
815 }
816
817 pub fn is_wireless_available(&self) -> Result<bool> {
819 let devices_variant = self.proxy.get(
821 InterfaceName::from_static_str_unchecked("org.freedesktop.NetworkManager"),
822 "Devices",
823 )?;
824
825 if let Some(zbus::zvariant::Value::Array(devices)) = devices_variant.downcast_ref() {
826 let device_values = devices.get();
827 for device in device_values {
828 if let zbus::zvariant::Value::ObjectPath(ref device_path) = device {
829 let device_props = zbus::blocking::fdo::PropertiesProxy::builder(&self.connection)
830 .destination("org.freedesktop.NetworkManager")?
831 .path(device_path)?
832 .build()?;
833
834 let device_type_variant = device_props.get(
835 InterfaceName::from_static_str_unchecked("org.freedesktop.NetworkManager.Device"),
836 "DeviceType",
837 )?;
838
839 if let Some(zbus::zvariant::Value::U32(device_type)) = device_type_variant.downcast_ref() {
840 if device_type == &2u32 { return Ok(true);
842 }
843 }
844 }
845 }
846 }
847 Ok(false)
848 }
849
850 pub fn listen_network_changes(&self) -> Result<mpsc::Receiver<NetworkInfo>> {
852 let (tx, rx) = mpsc::channel();
853 let connection_clone = self.connection.clone();
854 let app_handle = self.app.clone();
855
856 std::thread::spawn(move || {
858 match zbus::blocking::Connection::system() {
859 Ok(conn) => {
860 if let Ok(proxy) = zbus::blocking::Proxy::new(
862 &conn,
863 "org.freedesktop.NetworkManager",
864 "/org/freedesktop/NetworkManager",
865 "org.freedesktop.NetworkManager",
866 ) {
867 if let Ok(mut signal) = proxy.receive_signal("StateChanged") {
868 while let Some(_msg) = signal.next() {
869 let network_manager = VSKNetworkManager {
870 connection: connection_clone.clone(),
871 proxy: zbus::blocking::fdo::PropertiesProxy::builder(
872 &connection_clone,
873 )
874 .destination("org.freedesktop.NetworkManager")
875 .unwrap()
876 .path("/org/freedesktop/NetworkManager")
877 .unwrap()
878 .build()
879 .unwrap(),
880 app: app_handle.clone(),
881 };
882
883 if let Ok(network_info) =
884 network_manager.get_current_network_state()
885 {
886 if tx.send(network_info).is_err() {
887 break;
888 }
889 }
890 }
891 }
892 }
893 }
894 Err(e) => {
895 eprintln!(
896 "Error al conectar con D-Bus para escuchar cambios de red: {:?}",
897 e
898 );
899 }
900 }
901 });
902
903 Ok(rx)
904 }
905
906 pub fn disconnect_from_wifi(&self) -> Result<()> {
908 let _current_state = self.get_current_network_state()?;
910
911 let nm_proxy = zbus::blocking::Proxy::new(
913 &self.connection,
914 "org.freedesktop.NetworkManager",
915 "/org/freedesktop/NetworkManager",
916 "org.freedesktop.NetworkManager",
917 )?;
918
919 let active_connections_variant: zbus::zvariant::OwnedValue = self.proxy.get(
921 InterfaceName::from_static_str_unchecked("org.freedesktop.NetworkManager"),
922 "ActiveConnections",
923 )?;
924
925 let active_connections = match active_connections_variant.downcast_ref() {
927 Some(zbus::zvariant::Value::Array(arr)) => arr
928 .iter()
929 .filter_map(|v| match v {
930 zbus::zvariant::Value::ObjectPath(path) => {
931 Some(zbus::zvariant::OwnedObjectPath::from(path.to_owned()))
932 }
933 _ => None,
934 })
935 .collect::<Vec<zbus::zvariant::OwnedObjectPath>>(),
936 _ => Vec::new(),
937 };
938
939 if !active_connections.is_empty() {
940 nm_proxy.call::<_, _, ()>("DeactivateConnection", &(active_connections[0].as_str()))?;
941 Ok(())
942 } else {
943 Ok(())
944 }
945 }
946
947 pub fn get_saved_wifi_networks(&self) -> Result<Vec<NetworkInfo>> {
949 let settings_proxy = zbus::blocking::Proxy::new(
951 &self.connection,
952 "org.freedesktop.NetworkManager",
953 "/org/freedesktop/NetworkManager/Settings",
954 "org.freedesktop.NetworkManager.Settings",
955 )?;
956
957 let connections: Vec<zbus::zvariant::OwnedObjectPath> =
959 settings_proxy.call("ListConnections", &())?;
960 let mut saved_networks = Vec::new();
961
962 for conn_path in connections {
964 let conn_proxy = zbus::blocking::Proxy::new(
966 &self.connection,
967 "org.freedesktop.NetworkManager",
968 conn_path.as_str(),
969 "org.freedesktop.NetworkManager.Settings.Connection",
970 )?;
971
972 let settings: std::collections::HashMap<String, zbus::zvariant::OwnedValue> =
974 conn_proxy.call("GetSettings", &())?;
975
976 if let Some(connection) = settings.get("connection") {
978 let connection_value = connection.to_owned();
979 let connection_dict =
980 match <zbus::zvariant::Value<'_> as Clone>::clone(&connection_value)
981 .downcast::<std::collections::HashMap<String, zbus::zvariant::OwnedValue>>(
982 ) {
983 Some(dict) => dict,
984 _ => continue,
985 };
986
987 if let Some(conn_type) = connection_dict.get("type") {
989 let conn_type_value = conn_type.to_owned();
990 let conn_type_str =
991 match <zbus::zvariant::Value<'_> as Clone>::clone(&conn_type_value)
992 .downcast::<String>()
993 {
994 Some(s) => s,
995 _ => continue,
996 };
997
998 if conn_type_str == "802-11-wireless" {
1000 let mut network_info = NetworkInfo::default();
1001 network_info.connection_type = "wifi".to_string();
1002
1003 if let Some(id) = connection_dict.get("id") {
1005 let id_value = id.to_owned();
1006 if let Some(name) =
1007 <zbus::zvariant::Value<'_> as Clone>::clone(&id_value)
1008 .downcast::<String>()
1009 {
1010 network_info.name = name;
1011 }
1012 }
1013
1014 if let Some(wireless) = settings.get("802-11-wireless") {
1016 let wireless_value = wireless.to_owned();
1017 let wireless_dict = match <zbus::zvariant::Value<'_> as Clone>::clone(&wireless_value).downcast::<std::collections::HashMap<String, zbus::zvariant::OwnedValue>>() {
1018 Some(dict) => dict,
1019 _ => continue,
1020 };
1021
1022 if let Some(ssid) = wireless_dict.get("ssid") {
1023 let ssid_value = ssid.to_owned();
1024 if let Some(ssid_bytes) =
1025 <zbus::zvariant::Value<'_> as Clone>::clone(&ssid_value)
1026 .downcast::<Vec<u8>>()
1027 {
1028 if let Ok(ssid_str) = String::from_utf8(ssid_bytes) {
1029 network_info.ssid = ssid_str;
1030 }
1031 }
1032 }
1033 }
1034
1035 if let Some(security) = settings.get("802-11-wireless-security") {
1037 let security_value = security.to_owned();
1038 let security_dict = match <zbus::zvariant::Value<'_> as Clone>::clone(&security_value).downcast::<std::collections::HashMap<String, zbus::zvariant::OwnedValue>>() {
1039 Some(dict) => dict,
1040 _ => {
1041 network_info.security_type = WiFiSecurityType::None;
1042 saved_networks.push(network_info);
1043 continue;
1044 },
1045 };
1046
1047 if let Some(key_mgmt) = security_dict.get("key-mgmt") {
1048 let key_mgmt_value = key_mgmt.to_owned();
1049 if let Some(key_mgmt_str) =
1050 <zbus::zvariant::Value<'_> as Clone>::clone(&key_mgmt_value)
1051 .downcast::<String>()
1052 {
1053 match key_mgmt_str.as_str() {
1054 "none" => {
1055 network_info.security_type = WiFiSecurityType::None
1056 }
1057 "wpa-psk" => {
1058 network_info.security_type = WiFiSecurityType::WpaPsk
1059 }
1060 "wpa-eap" => {
1061 network_info.security_type = WiFiSecurityType::WpaEap
1062 }
1063 _ => network_info.security_type = WiFiSecurityType::None,
1064 }
1065 }
1066 }
1067 } else {
1068 network_info.security_type = WiFiSecurityType::None;
1069 }
1070
1071 saved_networks.push(network_info);
1073 }
1074 }
1075 }
1076 }
1077
1078 Ok(saved_networks)
1079 }
1080
1081 pub fn delete_wifi_connection(&self, ssid: &str) -> Result<bool> {
1083 let settings_proxy = zbus::blocking::Proxy::new(
1085 &self.connection,
1086 "org.freedesktop.NetworkManager",
1087 "/org/freedesktop/NetworkManager/Settings",
1088 "org.freedesktop.NetworkManager.Settings",
1089 )?;
1090
1091 let connections: Vec<zbus::zvariant::OwnedObjectPath> =
1093 settings_proxy.call("ListConnections", &())?;
1094
1095 for conn_path in connections {
1097 let conn_proxy = zbus::blocking::Proxy::new(
1099 &self.connection,
1100 "org.freedesktop.NetworkManager",
1101 conn_path.as_str(),
1102 "org.freedesktop.NetworkManager.Settings.Connection",
1103 )?;
1104
1105 let settings: std::collections::HashMap<String, zbus::zvariant::OwnedValue> =
1107 conn_proxy.call("GetSettings", &())?;
1108
1109 if let Some(connection) = settings.get("connection") {
1111 let connection_value = connection.to_owned();
1112 let connection_dict =
1113 match <zbus::zvariant::Value<'_> as Clone>::clone(&connection_value)
1114 .downcast::<std::collections::HashMap<String, zbus::zvariant::OwnedValue>>(
1115 ) {
1116 Some(dict) => dict,
1117 _ => continue,
1118 };
1119
1120 if let Some(conn_type) = connection_dict.get("type") {
1122 let conn_type_value = conn_type.to_owned();
1123 let conn_type_str =
1124 match <zbus::zvariant::Value<'_> as Clone>::clone(&conn_type_value)
1125 .downcast::<String>()
1126 {
1127 Some(s) => s,
1128 _ => continue,
1129 };
1130
1131 if conn_type_str == "802-11-wireless" {
1133 if let Some(wireless) = settings.get("802-11-wireless") {
1134 let wireless_value = wireless.to_owned();
1135 let wireless_dict = match <zbus::zvariant::Value<'_> as Clone>::clone(&wireless_value).downcast::<std::collections::HashMap<String, zbus::zvariant::OwnedValue>>() {
1136 Some(dict) => dict,
1137 _ => continue,
1138 };
1139
1140 if let Some(ssid_value) = wireless_dict.get("ssid") {
1141 let ssid_owned = ssid_value.to_owned();
1142 if let Some(ssid_bytes) =
1143 <zbus::zvariant::Value<'_> as Clone>::clone(&ssid_owned)
1144 .downcast::<Vec<u8>>()
1145 {
1146 if let Ok(conn_ssid_str) = String::from_utf8(ssid_bytes) {
1147 if conn_ssid_str == ssid {
1149 conn_proxy.call::<_, _, ()>("Delete", &())?;
1150 return Ok(true);
1151 }
1152 }
1153 }
1154 }
1155 }
1156 }
1157 }
1158 }
1159 }
1160
1161 Ok(false)
1163 }
1164
1165 pub fn list_vpn_profiles(&self) -> Result<Vec<VpnProfile>> {
1167 let connections = self.list_connection_paths()?;
1168 let mut profiles = Vec::new();
1169
1170 for conn_path in connections {
1171 let settings = self.get_connection_settings(&conn_path)?;
1172 if let Some(profile) = self.vpn_profile_from_settings(&settings) {
1173 profiles.push(profile);
1174 }
1175 }
1176
1177 profiles.sort_by(|a, b| a.id.cmp(&b.id));
1178 Ok(profiles)
1179 }
1180
1181 pub fn get_vpn_status(&self) -> Result<VpnStatus> {
1183 let active_connections_variant = self.proxy.get(
1184 InterfaceName::from_static_str_unchecked("org.freedesktop.NetworkManager"),
1185 "ActiveConnections",
1186 )?;
1187
1188 let mut status = VpnStatus::default();
1189
1190 if let Some(zbus::zvariant::Value::Array(arr)) = active_connections_variant.downcast_ref() {
1191 for value in arr.iter() {
1192 let active_path = match value {
1193 zbus::zvariant::Value::ObjectPath(path) => path,
1194 _ => continue,
1195 };
1196
1197 let active_props = zbus::blocking::fdo::PropertiesProxy::builder(&self.connection)
1198 .destination("org.freedesktop.NetworkManager")?
1199 .path(active_path)?
1200 .build()?;
1201
1202 let conn_type_variant = active_props.get(
1203 InterfaceName::from_static_str_unchecked(
1204 "org.freedesktop.NetworkManager.Connection.Active",
1205 ),
1206 "Type",
1207 )?;
1208
1209 let conn_type = match conn_type_variant.downcast_ref() {
1210 Some(zbus::zvariant::Value::Str(v)) => v.to_string(),
1211 _ => continue,
1212 };
1213
1214 if conn_type != "vpn" {
1215 continue;
1216 }
1217
1218 let state_variant = active_props.get(
1219 InterfaceName::from_static_str_unchecked(
1220 "org.freedesktop.NetworkManager.Connection.Active",
1221 ),
1222 "State",
1223 )?;
1224 let state = match state_variant.downcast_ref() {
1225 Some(zbus::zvariant::Value::U32(v)) => *v,
1226 _ => 0,
1227 };
1228
1229 let id_variant = active_props.get(
1230 InterfaceName::from_static_str_unchecked(
1231 "org.freedesktop.NetworkManager.Connection.Active",
1232 ),
1233 "Id",
1234 )?;
1235 let uuid_variant = active_props.get(
1236 InterfaceName::from_static_str_unchecked(
1237 "org.freedesktop.NetworkManager.Connection.Active",
1238 ),
1239 "Uuid",
1240 )?;
1241
1242 status.state = Self::vpn_state_from_active_state(state);
1243 status.active_profile_name = match id_variant.downcast_ref() {
1244 Some(zbus::zvariant::Value::Str(v)) => Some(v.to_string()),
1245 _ => None,
1246 };
1247 status.active_profile_id = status.active_profile_name.clone();
1248 status.active_profile_uuid = match uuid_variant.downcast_ref() {
1249 Some(zbus::zvariant::Value::Str(v)) => Some(v.to_string()),
1250 _ => None,
1251 };
1252
1253 let ip4_config_variant = active_props.get(
1254 InterfaceName::from_static_str_unchecked(
1255 "org.freedesktop.NetworkManager.Connection.Active",
1256 ),
1257 "Ip4Config",
1258 )?;
1259
1260 if let Some(zbus::zvariant::Value::ObjectPath(ip4_path)) =
1261 ip4_config_variant.downcast_ref()
1262 {
1263 if ip4_path.as_str() != "/" {
1264 let ip4_props = zbus::blocking::fdo::PropertiesProxy::builder(&self.connection)
1265 .destination("org.freedesktop.NetworkManager")?
1266 .path(ip4_path)?
1267 .build()?;
1268
1269 if let Ok(gateway_variant) = ip4_props.get(
1270 InterfaceName::from_static_str_unchecked(
1271 "org.freedesktop.NetworkManager.IP4Config",
1272 ),
1273 "Gateway",
1274 ) {
1275 status.gateway = match gateway_variant.downcast_ref() {
1276 Some(zbus::zvariant::Value::Str(v)) => Some(v.to_string()),
1277 _ => None,
1278 };
1279 }
1280 }
1281 }
1282
1283 return Ok(status);
1284 }
1285 }
1286
1287 Ok(status)
1288 }
1289
1290 pub fn connect_vpn(&self, uuid: String) -> Result<()> {
1292 let current_status = self.get_vpn_status()?;
1293 if current_status.state == VpnConnectionState::Connected
1294 && current_status.active_profile_uuid.as_deref() == Some(uuid.as_str())
1295 {
1296 return Err(crate::error::NetworkError::VpnAlreadyConnected(uuid));
1297 }
1298
1299 let conn_path = self.find_connection_path_by_uuid(&uuid)?;
1300
1301 let nm_proxy = zbus::blocking::Proxy::new(
1302 &self.connection,
1303 "org.freedesktop.NetworkManager",
1304 "/org/freedesktop/NetworkManager",
1305 "org.freedesktop.NetworkManager",
1306 )?;
1307
1308 let activate_result: zbus::Result<zbus::zvariant::OwnedObjectPath> =
1309 nm_proxy.call("ActivateConnection", &(conn_path.as_str(), "/", "/"));
1310
1311 match activate_result {
1312 Ok(_) => Ok(()),
1313 Err(e) => {
1314 let msg = e.to_string().to_lowercase();
1315 if msg.contains("secret") || msg.contains("authentication") {
1316 Err(crate::error::NetworkError::VpnAuthFailed(e.to_string()))
1317 } else {
1318 Err(crate::error::NetworkError::VpnActivationFailed(e.to_string()))
1319 }
1320 }
1321 }
1322 }
1323
1324 pub fn disconnect_vpn(&self, uuid: Option<String>) -> Result<()> {
1326 let active_connections_variant = self.proxy.get(
1327 InterfaceName::from_static_str_unchecked("org.freedesktop.NetworkManager"),
1328 "ActiveConnections",
1329 )?;
1330
1331 let mut target_active_connection: Option<zbus::zvariant::OwnedObjectPath> = None;
1332
1333 if let Some(zbus::zvariant::Value::Array(arr)) = active_connections_variant.downcast_ref() {
1334 for value in arr.iter() {
1335 let active_path = match value {
1336 zbus::zvariant::Value::ObjectPath(path) => {
1337 zbus::zvariant::OwnedObjectPath::from(path.to_owned())
1338 }
1339 _ => continue,
1340 };
1341
1342 let active_props = zbus::blocking::fdo::PropertiesProxy::builder(&self.connection)
1343 .destination("org.freedesktop.NetworkManager")?
1344 .path(active_path.as_str())?
1345 .build()?;
1346
1347 let conn_type_variant = active_props.get(
1348 InterfaceName::from_static_str_unchecked(
1349 "org.freedesktop.NetworkManager.Connection.Active",
1350 ),
1351 "Type",
1352 )?;
1353 let conn_type = match conn_type_variant.downcast_ref() {
1354 Some(zbus::zvariant::Value::Str(v)) => v.to_string(),
1355 _ => continue,
1356 };
1357
1358 if conn_type != "vpn" {
1359 continue;
1360 }
1361
1362 if let Some(target_uuid) = uuid.as_deref() {
1363 let uuid_variant = active_props.get(
1364 InterfaceName::from_static_str_unchecked(
1365 "org.freedesktop.NetworkManager.Connection.Active",
1366 ),
1367 "Uuid",
1368 )?;
1369 let active_uuid = match uuid_variant.downcast_ref() {
1370 Some(zbus::zvariant::Value::Str(v)) => v.to_string(),
1371 _ => continue,
1372 };
1373
1374 if active_uuid == target_uuid {
1375 target_active_connection = Some(active_path.clone());
1376 break;
1377 }
1378 } else {
1379 target_active_connection = Some(active_path.clone());
1380 break;
1381 }
1382 }
1383 }
1384
1385 let target_active_connection = match target_active_connection {
1386 Some(path) => path,
1387 None => {
1388 if let Some(target_uuid) = uuid {
1389 return Err(crate::error::NetworkError::VpnProfileNotFound(target_uuid));
1390 }
1391 return Err(crate::error::NetworkError::VpnNotActive);
1392 }
1393 };
1394
1395 let nm_proxy = zbus::blocking::Proxy::new(
1396 &self.connection,
1397 "org.freedesktop.NetworkManager",
1398 "/org/freedesktop/NetworkManager",
1399 "org.freedesktop.NetworkManager",
1400 )?;
1401
1402 nm_proxy.call::<_, _, ()>(
1403 "DeactivateConnection",
1404 &(target_active_connection.as_str(),),
1405 )?;
1406 Ok(())
1407 }
1408
1409 pub fn create_vpn_profile(&self, config: VpnCreateConfig) -> Result<VpnProfile> {
1411 if config.id.trim().is_empty() {
1412 return Err(crate::error::NetworkError::VpnInvalidConfig(
1413 "id is required".to_string(),
1414 ));
1415 }
1416
1417 let uuid = Uuid::new_v4().to_string();
1418 let mut connection_section = HashMap::new();
1419 connection_section.insert("id".to_string(), Value::from(config.id.clone()));
1420 connection_section.insert("uuid".to_string(), Value::from(uuid.clone()));
1421 connection_section.insert("type".to_string(), Value::from("vpn"));
1422 connection_section.insert(
1423 "autoconnect".to_string(),
1424 Value::from(config.autoconnect.unwrap_or(false)),
1425 );
1426
1427 let mut vpn_section = HashMap::new();
1428 vpn_section.insert(
1429 "service-type".to_string(),
1430 Value::from(Self::service_type_from_vpn_type(&config.vpn_type)),
1431 );
1432
1433 if let Some(username) = config.username {
1434 vpn_section.insert("user-name".to_string(), Value::from(username));
1435 }
1436 if let Some(gateway) = config.gateway {
1437 vpn_section.insert("remote".to_string(), Value::from(gateway));
1438 }
1439 if let Some(ca_cert_path) = config.ca_cert_path {
1440 vpn_section.insert("ca".to_string(), Value::from(ca_cert_path));
1441 }
1442 if let Some(user_cert_path) = config.user_cert_path {
1443 vpn_section.insert("cert".to_string(), Value::from(user_cert_path));
1444 }
1445 if let Some(private_key_path) = config.private_key_path {
1446 vpn_section.insert("key".to_string(), Value::from(private_key_path));
1447 }
1448 if let Some(private_key_password) = config.private_key_password {
1449 vpn_section.insert("key-password".to_string(), Value::from(private_key_password));
1450 }
1451 if let Some(custom_settings) = config.settings {
1452 for (k, v) in custom_settings {
1453 vpn_section.insert(k, Value::from(v));
1454 }
1455 }
1456
1457 let mut vpn_secrets_section = HashMap::new();
1458 if let Some(password) = config.password {
1459 vpn_secrets_section.insert("password".to_string(), Value::from(password));
1460 }
1461 if let Some(custom_secrets) = config.secrets {
1462 for (k, v) in custom_secrets {
1463 vpn_secrets_section.insert(k, Value::from(v));
1464 }
1465 }
1466
1467 let mut settings: HashMap<String, HashMap<String, Value>> = HashMap::new();
1468 settings.insert("connection".to_string(), connection_section);
1469 settings.insert("vpn".to_string(), vpn_section);
1470 if !vpn_secrets_section.is_empty() {
1471 settings.insert("vpn-secrets".to_string(), vpn_secrets_section);
1472 }
1473
1474 let settings_proxy = zbus::blocking::Proxy::new(
1475 &self.connection,
1476 "org.freedesktop.NetworkManager",
1477 "/org/freedesktop/NetworkManager/Settings",
1478 "org.freedesktop.NetworkManager.Settings",
1479 )?;
1480
1481 let _created_path: zbus::zvariant::OwnedObjectPath =
1482 settings_proxy.call("AddConnection", &(settings,))?;
1483
1484 Ok(VpnProfile {
1485 id: config.id,
1486 uuid,
1487 vpn_type: config.vpn_type,
1488 interface_name: None,
1489 autoconnect: config.autoconnect.unwrap_or(false),
1490 editable: true,
1491 last_error: None,
1492 })
1493 }
1494
1495 pub fn update_vpn_profile(&self, config: VpnUpdateConfig) -> Result<VpnProfile> {
1497 let conn_path = self.find_connection_path_by_uuid(&config.uuid)?;
1498 let existing_settings = self.get_connection_settings(&conn_path)?;
1499
1500 let existing_profile = self
1501 .vpn_profile_from_settings(&existing_settings)
1502 .ok_or_else(|| crate::error::NetworkError::VpnProfileNotFound(config.uuid.clone()))?;
1503
1504 let existing_vpn_settings = Self::string_map_from_section(&existing_settings, "vpn");
1505 let existing_vpn_secrets = Self::string_map_from_section(&existing_settings, "vpn-secrets");
1506
1507 let mut settings: HashMap<String, HashMap<String, Value>> = HashMap::new();
1510 for (section_name, section_value) in &existing_settings {
1511 let raw_value = section_value.to_owned();
1512 let dict = match <zbus::zvariant::Value<'_> as Clone>::clone(&raw_value)
1513 .downcast::<HashMap<String, zbus::zvariant::OwnedValue>>()
1514 {
1515 Some(d) => d,
1516 None => continue,
1517 };
1518
1519 let mut section_map: HashMap<String, Value> = HashMap::new();
1520 for (k, v) in dict {
1521 section_map.insert(k, <zbus::zvariant::Value<'_> as Clone>::clone(&v));
1522 }
1523 settings.insert(section_name.clone(), section_map);
1524 }
1525
1526 let connection_section = settings
1527 .entry("connection".to_string())
1528 .or_insert_with(HashMap::new);
1529 connection_section.insert(
1530 "id".to_string(),
1531 Value::from(config.id.clone().unwrap_or(existing_profile.id.clone())),
1532 );
1533 connection_section.insert("uuid".to_string(), Value::from(config.uuid.clone()));
1534 connection_section.insert("type".to_string(), Value::from("vpn"));
1535 connection_section.insert(
1536 "autoconnect".to_string(),
1537 Value::from(config.autoconnect.unwrap_or(existing_profile.autoconnect)),
1538 );
1539
1540 let service_type = existing_vpn_settings
1541 .get("service-type")
1542 .cloned()
1543 .unwrap_or_else(|| {
1544 Self::service_type_from_vpn_type(&existing_profile.vpn_type).to_string()
1545 });
1546
1547 let vpn_section = settings
1548 .entry("vpn".to_string())
1549 .or_insert_with(HashMap::new);
1550 vpn_section.insert("service-type".to_string(), Value::from(service_type));
1551
1552 let mut merged_settings = existing_vpn_settings;
1553 if let Some(username) = config.username {
1554 merged_settings.insert("user-name".to_string(), username);
1555 }
1556 if let Some(gateway) = config.gateway {
1557 merged_settings.insert("remote".to_string(), gateway);
1558 }
1559 if let Some(ca_cert_path) = config.ca_cert_path {
1560 merged_settings.insert("ca".to_string(), ca_cert_path);
1561 }
1562 if let Some(user_cert_path) = config.user_cert_path {
1563 merged_settings.insert("cert".to_string(), user_cert_path);
1564 }
1565 if let Some(private_key_path) = config.private_key_path {
1566 merged_settings.insert("key".to_string(), private_key_path);
1567 }
1568 if let Some(private_key_password) = config.private_key_password {
1569 merged_settings.insert("key-password".to_string(), private_key_password);
1570 }
1571 if let Some(custom_settings) = config.settings {
1572 for (k, v) in custom_settings {
1573 merged_settings.insert(k, v);
1574 }
1575 }
1576
1577 for (k, v) in merged_settings {
1578 vpn_section.insert(k, Value::from(v));
1579 }
1580
1581 let mut merged_secrets = existing_vpn_secrets;
1582 if let Some(password) = config.password {
1583 merged_secrets.insert("password".to_string(), password);
1584 }
1585 if let Some(custom_secrets) = config.secrets {
1586 for (k, v) in custom_secrets {
1587 merged_secrets.insert(k, v);
1588 }
1589 }
1590
1591 if merged_secrets.is_empty() {
1592 settings.remove("vpn-secrets");
1593 } else {
1594 let vpn_secrets_section = settings
1595 .entry("vpn-secrets".to_string())
1596 .or_insert_with(HashMap::new);
1597 vpn_secrets_section.clear();
1598 for (k, v) in merged_secrets {
1599 vpn_secrets_section.insert(k, Value::from(v));
1600 }
1601 }
1602
1603 let conn_proxy = zbus::blocking::Proxy::new(
1604 &self.connection,
1605 "org.freedesktop.NetworkManager",
1606 conn_path.as_str(),
1607 "org.freedesktop.NetworkManager.Settings.Connection",
1608 )?;
1609 conn_proxy.call::<_, _, ()>("Update", &(settings,))?;
1610
1611 let updated_settings = self.get_connection_settings(&conn_path)?;
1612 self.vpn_profile_from_settings(&updated_settings)
1613 .ok_or_else(|| crate::error::NetworkError::VpnProfileNotFound(config.uuid))
1614 }
1615
1616 pub fn delete_vpn_profile(&self, uuid: String) -> Result<()> {
1618 let conn_path = self.find_connection_path_by_uuid(&uuid)?;
1619
1620 let conn_proxy = zbus::blocking::Proxy::new(
1621 &self.connection,
1622 "org.freedesktop.NetworkManager",
1623 conn_path.as_str(),
1624 "org.freedesktop.NetworkManager.Settings.Connection",
1625 )?;
1626
1627 conn_proxy.call::<_, _, ()>("Delete", &())?;
1628 Ok(())
1629 }
1630}
1631
1632#[cfg(test)]
1633mod tests {
1634 use super::*;
1635
1636 #[test]
1637 fn vpn_state_deactivated_maps_to_disconnected() {
1638 assert_eq!(
1639 VSKNetworkManager::<tauri::Wry>::vpn_state_from_active_state(4),
1640 VpnConnectionState::Disconnected
1641 );
1642 }
1643}
1644
1645pub async fn init(
1647 app: &AppHandle<tauri::Wry>,
1648 _api: PluginApi<tauri::Wry, ()>,
1649) -> Result<VSKNetworkManager<'static, tauri::Wry>> {
1650 Ok(VSKNetworkManager::new(app.clone()).await?)
1651}